diff --git a/manual/includes/_session.md b/manual/includes/_session.md index 460042cd..06b856c7 100644 --- a/manual/includes/_session.md +++ b/manual/includes/_session.md @@ -8,16 +8,37 @@ Session variables keep information about one single user, and are available to a ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: files - save_path: /tmp/sessions + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` -- `name`: Name of the session. -- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours). -- `save_handler`: Specifies the session storage mechanism (in this case, files). -- `save_path`: Directory where session files are stored. +### Explanation: + +- **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier. + +- **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires. + +- **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration. + +- **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler). + +- **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration. + +- **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set. + +- **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections. + +- **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie. + +- **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session. + **PHP Script** @@ -68,10 +89,15 @@ For example: ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: redis + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` `${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key. diff --git a/manual/index.html b/manual/index.html index 431c64cb..8eca2dd3 100644 --- a/manual/index.html +++ b/manual/index.html @@ -1989,15 +1989,44 @@
Yaml File
session:
- name: MUSICPRODUCTIONMANAGER
- max_life_time: 86400
- save_handler: files
- save_path: /tmp/sessions
+ name: ${SESSION_NAME}
+ max_life_time: ${SESSION_MAX_LIFE_TIME}
+ save_handler: ${SESSION_SAVE_HANDLER}
+ save_path: ${SESSION_SAVE_PATH}
+ cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+ cookie_path: ${SESSION_COOKIE_PATH}
+ cookie_secure: ${SESSION_COOKIE_SECURE}
+ cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+ cookie_domain: ${SESSION_COOKIE_DOMAIN}
+name: Name of the session.max_life_time: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours).save_handler: Specifies the session storage mechanism (in this case, files).save_path: Directory where session files are stored.name: Specifies the name of the session. The value ${SESSION_NAME} refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier.
max_life_time: Defines the maximum lifetime of the session in seconds. The value ${SESSION_MAX_LIFE_TIME} is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires.
save_handler: Specifies the handler used to save session data. In this case, ${SESSION_SAVE_HANDLER} likely refers to the session handler, such as redis, files, or database, depending on the server configuration.
save_path: Defines the path where session data will be stored. The value ${SESSION_SAVE_PATH} is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if redis is used as the save handler).
cookie_lifetime: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. ${SESSION_COOKIE_LIFETIME} is the environment variable or configuration value that sets this duration.
cookie_path: The path on the server where the session cookie is valid. ${SESSION_COOKIE_PATH} determines the directory or URL prefix for which the session cookie is set.
cookie_secure: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If ${SESSION_COOKIE_SECURE} is set to true, the cookie will only be sent over secure connections.
cookie_httponly: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If ${SESSION_COOKIE_HTTPONLY} is true, it increases security by preventing client-side access to the cookie.
cookie_domain: Specifies the domain to which the session cookie is valid. ${SESSION_COOKIE_DOMAIN} can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session.
PHP Script
<?php
@@ -2031,10 +2060,15 @@ Session with Redis
You can not encrypt the ${REDIS_AUTH} value. If you want to secure the config, encrypt entire save_path instead.
For example:
session:
- name: MUSICPRODUCTIONMANAGER
- max_life_time: 86400
- save_handler: redis
- save_path: ${SESSION_SAVE_PATH}
+ name: ${SESSION_NAME}
+ max_life_time: ${SESSION_MAX_LIFE_TIME}
+ save_handler: ${SESSION_SAVE_HANDLER}
+ save_path: ${SESSION_SAVE_PATH}
+ cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+ cookie_path: ${SESSION_COOKIE_PATH}
+ cookie_secure: ${SESSION_COOKIE_SECURE}
+ cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+ cookie_domain: ${SESSION_COOKIE_DOMAIN}
${SESSION_SAVE_PATH} contains entire of save_path that encrypted with you secure key.
PHP Script
<?php
diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php
index 77c2ee63..7b316f38 100644
--- a/src/Database/PicoDatabase.php
+++ b/src/Database/PicoDatabase.php
@@ -1056,6 +1056,33 @@ public function getDatabaseType()
return $this->databaseType;
}
+ /**
+ * Retrieves the time zone used by the database.
+ *
+ * This function calls the `getTimeZone()` method from the `databaseCredentials`
+ * object to fetch the time zone configured for the database connection.
+ *
+ * @return string The time zone of the database (e.g., "UTC", "America/New_York").
+ */
+ public function getDatabaseTimeZone()
+ {
+ return $this->databaseCredentials->getTimeZone();
+ }
+
+ /**
+ * Retrieves the time zone offset of the database connection.
+ *
+ * This function retrieves the time zone offset by calling the static method
+ * `getTimeZoneOffset()` with the `databaseConnection` as an argument.
+ * The offset is returned in seconds from UTC.
+ *
+ * @return string The time zone offset, typically in hours and minutes (e.g., "+02:00").
+ */
+ public function getDatabaseTimeZoneOffset()
+ {
+ return self::getTimeZoneOffset($this->databaseConnection);
+ }
+
/**
* Convert the object to a JSON string representation for debugging.
*
diff --git a/src/Session/PicoSession.php b/src/Session/PicoSession.php
index ffcfc2ad..8540c64c 100644
--- a/src/Session/PicoSession.php
+++ b/src/Session/PicoSession.php
@@ -195,26 +195,29 @@ public function destroy()
}
/**
- * Sets cookie parameters for the session.
+ * Sets the session cookie parameters, including lifetime, path, domain, security attributes, and SameSite settings.
*
- * This method sets parameters for the session cookie, including lifetime,
- * security attributes, and SameSite settings.
+ * This method configures the session cookie parameters such as maximum lifetime, path, domain, and security settings
+ * like whether the cookie should be accessible only over HTTPS or only via HTTP. It also sets the SameSite attribute
+ * for compatibility with different browsers and PHP versions.
*
- * @param int $maxlifetime Maximum lifetime of the session cookie.
+ * @param int $maxlifetime Maximum lifetime of the session cookie in seconds.
+ * @param string $path The path where the cookie will be available on the server.
+ * @param string $domain The domain to which the cookie is available.
* @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection.
- * @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol.
- * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None).
+ * @param bool $httponly Indicates if the cookie should be accessible only through the HTTP protocol.
+ * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'.
* @return self Returns the current instance for method chaining.
*/
- public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesite = self::SAME_SITE_STRICT)
+ public function setSessionCookieParams($maxlifetime, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT)
{
if (PHP_VERSION_ID < 70300) {
- session_set_cookie_params($maxlifetime, '/; samesite=' . $samesite, $_SERVER['HTTP_HOST'], $secure, $httponly);
+ session_set_cookie_params($maxlifetime, $path.'; samesite=' . $samesite, $domain, $secure, $httponly);
} else {
session_set_cookie_params(array(
'lifetime' => $maxlifetime,
- 'path' => '/',
- 'domain' => $_SERVER['HTTP_HOST'],
+ 'path' => $path,
+ 'domain' => $domain,
'secure' => $secure,
'httponly' => $httponly,
'samesite' => $samesite
@@ -224,18 +227,19 @@ public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesi
}
/**
- * Sets a cookie with SameSite attribute support for different PHP versions.
+ * Sets a cookie with the SameSite attribute, supporting both older and newer PHP versions.
*
- * This method sets a cookie with the specified SameSite attribute, supporting both older and newer PHP versions.
+ * This method sets a cookie with a specified SameSite attribute, ensuring compatibility with both PHP versions
+ * prior to and after PHP 7.3. It supports cookies with the 'Lax', 'Strict', or 'None' SameSite attributes.
*
* @param string $name The name of the cookie.
* @param string $value The value of the cookie.
- * @param int $expire The expiration time of the cookie.
- * @param string $path The path on the server in which the cookie will be available.
- * @param string $domain The domain that the cookie is available to.
+ * @param int $expire The expiration time of the cookie as a Unix timestamp.
+ * @param string $path The path on the server where the cookie is available.
+ * @param string $domain The domain to which the cookie is available.
* @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection.
* @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol.
- * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None).
+ * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'.
* @return self Returns the current instance for method chaining.
*/
public function setSessionCookieSameSite($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT) // NOSONAR
@@ -355,4 +359,18 @@ public function setSessionId($id)
@session_id($id);
return $this;
}
+
+ /**
+ * Converts the session data to a string representation.
+ *
+ * This method returns the session data as a JSON-encoded string, which can be useful for debugging
+ * or logging the contents of the session. It encodes the global `$_SESSION` array into a JSON string.
+ *
+ * @return string The JSON-encoded string representation of the session data.
+ */
+ public function __toString()
+ {
+ return json_encode($_SESSION);
+ }
+
}
diff --git a/src/Util/Database/PicoQueryTranslator.php b/src/Util/Database/PicoQueryTranslator.php
new file mode 100644
index 00000000..39ecff4a
--- /dev/null
+++ b/src/Util/Database/PicoQueryTranslator.php
@@ -0,0 +1,842 @@
+dbToSqlite = [
+ "int" => "INTEGER",
+ "tinyint(1)" => "BOOLEAN", // NOSONAR
+ "tinyint" => "INTEGER",
+ "smallint" => "INTEGER",
+ "mediumint" => "INTEGER",
+ "bigint" => "INTEGER",
+ "real" => "REAL",
+ "float" => "REAL",
+ "double" => "REAL",
+ "decimal" => "REAL",
+ "nvarchar" => "NVARCHAR",
+ "varchar" => "NVARCHAR",
+ "character varying" => "NVARCHAR", // NOSONAR
+ "char" => "NVARCHAR",
+ "tinytext" => "TEXT",
+ "mediumtext" => "TEXT",
+ "longtext" => "TEXT",
+ "text" => "TEXT",
+ "datetime" => "DATETIME",
+ "timestamp" => "TIMESTAMP",
+ "date" => "DATE",
+ "time" => "TIME",
+ "year" => "INTEGER",
+ "boolean" => "INTEGER",
+ "json" => "TEXT",
+ "jsonb" => "TEXT",
+ "integer" => "INTEGER",
+ "serial" => "INTEGER",
+ "bigserial" => "INTEGER",
+ "double precision" => "REAL",
+ "timestamptz" => "TIMESTAMP"
+ ];
+
+ $this->dbToMySQL = [
+ "bigint" => "BIGINT",
+ "mediumint" => "MEDIUMINT",
+ "smallint" => "SMALLINT",
+ "integer" => "INT",
+ "double" => "DOUBLE",
+ "float" => "FLOAT",
+ "real" => "DOUBLE",
+ "decimal" => "DECIMAL",
+ "numeric" => "NUMERIC",
+ "tinytext" => "TINYTEXT",
+ "mediumtext" => "MEDIUMTEXT",
+ "longtext" => "LONGTEXT",
+ "text" => "TEXT",
+ "nvarchar" => "VARCHAR",
+ "varchar" => "VARCHAR",
+ "character varying" => "VARCHAR",
+ "tinyint(1)" => "TINYINT(1)", // NOSONAR
+ "tinyint" => "TINYINT",
+ "boolean" => "TINYINT(1)",
+ "int" => "INT",
+ "datetime" => "DATETIME",
+ "date" => "DATE",
+ "timestamptz" => "TIMESTAMP",
+ "timestamp with time zone" => "TIMESTAMP",
+ "timestamp without time zone" => "DATETIME",
+ "timestamp" => "TIMESTAMPTZ",
+ "json" => "JSON",
+ "enum" => "ENUM",
+ "set" => "SET",
+ "char" => "CHAR"
+ ];
+
+ $this->dbToPostgreSQL = [
+ "bigint" => "BIGINT",
+ "mediumint" => "INTEGER",
+ "smallint" => "INTEGER",
+ "tinyint(1)" => "BOOLEAN",
+ "tinyint" => "INTEGER",
+ "integer" => "INTEGER",
+ "real" => "REAL",
+ "longtext" => "TEXT",
+ "mediumtext" => "TEXT",
+ "smalltext" => "TEXT",
+ "tinytext" => "TEXT",
+ "text" => "TEXT",
+ "character varying" => "CHARACTER VARYING", // NOSONAR
+ "nvarchar" => "CHARACTER VARYING",
+ "varchar" => "CHARACTER VARYING",
+ "char" => "CHARACTER",
+ "boolean" => "BOOLEAN",
+ "datetime" => "TIMESTAMP WITHOUT TIME ZONE",
+ "date" => "DATE",
+ "timestamptz" => "TIMESTAMP WITH TIME ZONE",
+ "timestamp" => "TIMESTAMP WITH TIME ZONE",
+ "time" => "TIME",
+ "json" => "JSONB"
+ ];
+ }
+
+ /**
+ * Replaces all occurrences of a substring in the given string.
+ *
+ * This function uses a regular expression to replace all matches of the search pattern with the specified replacement.
+ *
+ * @param string|null $str The string to search and replace in. If null, returns null.
+ * @param string $search The substring to search for.
+ * @param string $replacement The substring to replace the search pattern with.
+ * @return string|null The modified string, or null if the input string is null.
+ */
+ private function replaceAll($str, $search, $replacement) {
+ return str_ireplace($search, $replacement, $str);
+ }
+
+ /**
+ * Translates the provided SQL query to a specified target database format.
+ *
+ * This method converts the provided SQL string into a format compatible with the specified target database (MySQL, PostgreSQL, or SQLite),
+ * ensuring that all relevant data types and structures are adapted.
+ *
+ * @param string $value The SQL query to be translated.
+ * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+ * @return string The translated SQL query in the target database format.
+ */
+ public function translate($value, $targetType) {
+ $dropTables = [];
+ $tableInfo = $this->extractDropTableQueries($value, $targetType);
+
+ foreach ($tableInfo as $table) {
+ $dropTables[] = "-- DROP TABLE IF EXISTS " . $table['table'] . ";";
+ }
+
+ $value = $this->replaceAll($value, '`', '');
+ $value = $this->replaceAll($value, ' timestamp with time zone', ' timestamptz');
+ $value = $this->replaceAll($value, ' timestamp without time zone', ' timestamp');
+ $value = $this->replaceAll($value, ' character varying', ' varchar');
+ $value = $this->replaceAll($value, ' COLLATE pg_catalog."default"', '');
+ $value = $this->replaceAll($value, ' TINYINT(1)', ' boolean');
+
+ $tableParser = new PicoTableParser(); // Assuming this is a predefined class in your code
+ $tableParser->parseAll($value);
+ $tables = $tableParser->getResult();
+
+ $lines = [];
+ foreach ($tables as $table) {
+ $convertedTable = $this->convertQuery($table, $targetType);
+ $lines[] = $convertedTable;
+ $lines[] = '';
+ }
+
+ if (!empty($dropTables)) {
+ $dropTables[] = "\r\n\r\n";
+ }
+
+ return implode("\r\n", $dropTables) . implode("\r\n", $lines);
+ }
+
+ /**
+ * Converts a table schema to a query compatible with the specified database type.
+ *
+ * This method takes the parsed table schema and converts it to the corresponding SQL syntax for the target database.
+ *
+ * @param array $table The table schema to convert.
+ * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+ * @return string The converted table schema in the target database format.
+ */
+ private function convertQuery($table, $targetType) {
+ if ($this->isSQLite($targetType)) {
+ return $this->toSqliteOut($table, $targetType);
+ } elseif ($this->isMySQL($targetType)) {
+ return $this->toMySQLOut($table, $targetType);
+ } elseif ($this->isPGSQL($targetType)) {
+ return $this->toPostgreSQLOut($table, $targetType);
+ }
+ }
+
+ /**
+ * Converts a table schema to SQLite-specific SQL syntax.
+ *
+ * This method adapts the provided table schema to SQLite format, including data type conversions and primary key handling.
+ *
+ * @param array $table The table schema to convert.
+ * @param string $targetType The target database type ('sqlite').
+ * @return string The converted table schema in SQLite format.
+ */
+ private function toSqliteOut($table, $targetType) {
+ $sqliteTable = [
+ 'tableName' => $table['tableName'],
+ 'primaryKey' => $table['primaryKey'],
+ 'columns' => array_map(function($column) {
+ $column['Type'] = $this->toSqliteType($column['Type'], $column['Length']);
+ return $column;
+ }, $table['columns'])
+ ];
+ return $this->toSqliteTable($sqliteTable, $targetType);
+ }
+
+ /**
+ * Converts a table schema to MySQL-specific SQL syntax.
+ *
+ * This method adapts the provided table schema to MySQL format, including data type conversions and primary key handling.
+ *
+ * @param array $table The table schema to convert.
+ * @param string $targetType The target database type ('mysql').
+ * @return string The converted table schema in MySQL format.
+ */
+ private function toMySQLOut($table, $targetType) {
+ $mysqlTable = [
+ 'tableName' => $table['tableName'],
+ 'primaryKey' => $table['primaryKey'],
+ 'columns' => array_map(function($column) {
+ $column['Type'] = $this->toMySQLType($column['Type'], $column['Length']);
+ return $column;
+ }, $table['columns'])
+ ];
+ return $this->toMySQLTable($mysqlTable, $targetType);
+ }
+
+ /**
+ * Converts a table schema to PostgreSQL-specific SQL syntax.
+ *
+ * This method adapts the provided table schema to PostgreSQL format, including data type conversions and primary key handling.
+ *
+ * @param array $table The table schema to convert.
+ * @param string $targetType The target database type ('pgsql').
+ * @return string The converted table schema in PostgreSQL format.
+ */
+ private function toPostgreSQLOut($table, $targetType) {
+ $pgTable = [
+ 'tableName' => $table['tableName'],
+ 'primaryKey' => $table['primaryKey'],
+ 'columns' => array_map(function($column) {
+ $column['Type'] = $this->toPostgreSQLType($column['Type'], $column['Length']);
+ return $column;
+ }, $table['columns'])
+ ];
+ return $this->toPostgreSQLTable($pgTable, $targetType);
+ }
+
+
+ /**
+ * Converts a given table for SQLite target type.
+ *
+ * This method uses a common `toTable` function to convert a table for SQLite.
+ *
+ * @param string $sqliteTable The name of the SQLite table.
+ * @param string $targetType The target database type (e.g., 'sqlite').
+ * @return mixed The result of the `toTable` method.
+ */
+ private function toSqliteTable($sqliteTable, $targetType) {
+ return $this->toTable($sqliteTable, $targetType);
+ }
+
+ /**
+ * Converts a given table for MySQL target type.
+ *
+ * This method uses a common `toTable` function to convert a table for MySQL.
+ *
+ * @param string $mysqlTable The name of the MySQL table.
+ * @param string $targetType The target database type (e.g., 'mysql').
+ * @return mixed The result of the `toTable` method.
+ */
+ private function toMySQLTable($mysqlTable, $targetType) {
+ return $this->toTable($mysqlTable, $targetType);
+ }
+
+ /**
+ * Converts a given table for PostgreSQL target type.
+ *
+ * This method uses a common `toTable` function to convert a table for PostgreSQL.
+ *
+ * @param string $pgTable The name of the PostgreSQL table.
+ * @param string $targetType The target database type (e.g., 'pgsql').
+ * @return mixed The result of the `toTable` method.
+ */
+ private function toPostgreSQLTable($pgTable, $targetType) {
+ return $this->toTable($pgTable, $targetType);
+ }
+
+ /**
+ * Checks if the target type is MySQL or MariaDB.
+ *
+ * This method verifies if the target type matches MySQL or MariaDB.
+ *
+ * @param string $targetType The target database type.
+ * @return bool Returns true if the target type is 'mysql' or 'mariadb', otherwise false.
+ */
+ private function isMySQL($targetType) {
+ return $targetType === 'mysql' || $targetType === 'mariadb';
+ }
+
+ /**
+ * Checks if the target type is PostgreSQL.
+ *
+ * This method verifies if the target type matches PostgreSQL or PGSQL.
+ *
+ * @param string $targetType The target database type.
+ * @return bool Returns true if the target type is 'pgsql' or 'postgresql', otherwise false.
+ */
+ private function isPGSQL($targetType) {
+ return $targetType === 'pgsql' || $targetType === 'postgresql';
+ }
+
+ /**
+ * Checks if the target type is SQLite.
+ *
+ * This method verifies if the target type matches SQLite.
+ *
+ * @param string $targetType The target database type.
+ * @return bool Returns true if the target type is 'sqlite', otherwise false.
+ */
+ private function isSQLite($targetType) {
+ return $targetType === 'sqlite';
+ }
+
+ /**
+ * Checks if the given column type is a real number (e.g., FLOAT, DOUBLE, REAL, DECIMAL).
+ *
+ * This method checks if the column type corresponds to a real number type.
+ *
+ * @param string $columnType The column type to check.
+ * @return bool Returns true if the column type is a real number, otherwise false.
+ */
+ private function isReal($columnType) {
+ return stripos($columnType, 'FLOAT') !== false ||
+ stripos($columnType, 'DOUBLE') !== false ||
+ stripos($columnType, 'REAL') !== false ||
+ stripos($columnType, 'DECIMAL') !== false;
+ }
+
+ /**
+ * Checks if the given column type is a boolean type (e.g., BOOLEAN, BOOL, TINYINT(1)).
+ *
+ * This method checks if the column type corresponds to a boolean type.
+ *
+ * @param string $columnType The column type to check.
+ * @return bool Returns true if the column type is boolean, otherwise false.
+ */
+ private function isBoolean($columnType) {
+ return strtoupper($columnType) == 'BOOLEAN' ||
+ strtoupper($columnType) == 'BOOL' ||
+ strtoupper($columnType) == 'TINYINT(1)';
+ }
+
+ /**
+ * Converts a column type to the SQLite type format.
+ * @param string $type The original column type.
+ * @param int|null $length The column length (optional).
+ * @return string The converted SQLite column type.
+ */
+ public function toSqliteType($type, $length = null) {
+ $type = strtolower($type);
+
+ if ($type === 'tinyint' && $length === 1) {
+ return 'BOOLEAN';
+ }
+
+ $sqliteType = 'TEXT';
+ foreach ($this->dbToSqlite as $key => $value) {
+ if (strpos($type, strtolower($key)) === 0) {
+ $sqliteType = $value;
+ break;
+ }
+ }
+
+ if (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) {
+ $parsedEnum = $this->parseEnumValue($length);
+ $sqliteType = 'NVARCHAR(' . ($parsedEnum['maxLength'] + 2) . ')';
+ } elseif (($sqliteType === 'NVARCHAR' || $sqliteType === 'INT') && $length > 0) {
+ $sqliteType .= "($length)";
+ }
+
+ return $sqliteType;
+ }
+
+ /**
+ * Converts a column type to the MySQL type format.
+ * @param string $type The original column type.
+ * @param int|null $length The column length (optional).
+ * @return string The converted MySQL column type.
+ */
+ public function toMySQLType($type, $length = null) {
+ $type = strtolower($type);
+ $mysqlType = 'TEXT';
+
+ if ($this->isTinyInt1($type, $length)) {
+ return 'TINYINT(1)';
+ }
+
+ if ($this->isInteger($type) && $length > 0) {
+ return "{$type}($length)";
+ }
+
+ foreach ($this->dbToMySQL as $key => $value) {
+ if (strpos($type, strtolower($key)) === 0) {
+ $mysqlType = $value;
+ break;
+ }
+ }
+
+ $mysqlType = str_replace('TIMESTAMPTZ', 'TIMESTAMP', $mysqlType);
+
+ if (strpos(strtoupper($type), 'ENUM') !== false) {
+ $parsedEnum = $this->parseEnumValue($length);
+ $mysqlType = 'ENUM(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')';
+ } elseif (strpos(strtoupper($type), 'SET') !== false) {
+ $parsedEnum = $this->parseEnumValue($length);
+ $mysqlType = 'SET(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')';
+ } elseif (strpos(strtoupper($type), 'DECIMAL') !== false) {
+ $parsedNumeric = $this->parseNumericType($length);
+ $mysqlType = 'DECIMAL(' . implode(', ', $parsedNumeric['resultArray']) . ')';
+ } elseif (strpos(strtoupper($type), 'NUMERIC') !== false) {
+ $parsedNumeric = $this->parseNumericType($length);
+ $mysqlType = 'NUMERIC(' . implode(', ', $parsedNumeric['resultArray']) . ')';
+ }
+
+ if (($mysqlType === 'VARCHAR' || $mysqlType === 'CHAR') && $length > 0) {
+ $mysqlType .= "($length)";
+ }
+
+ return $mysqlType;
+ }
+
+ /**
+ * Converts a column type to the PostgreSQL type format.
+ * @param string $type The original column type.
+ * @param int|null $length The column length (optional).
+ * @return string The converted PostgreSQL column type.
+ */
+ public function toPostgreSQLType($type, $length = null) {
+ $type = strtolower($type);
+ $pgType = 'TEXT';
+
+ foreach ($this->dbToPostgreSQL as $key => $value) {
+ if (strpos($type, strtolower($key)) === 0) {
+ $pgType = $value;
+ break;
+ }
+ }
+ $pgType = strtoupper($pgType);
+ if (strpos(strtoupper($type), 'TINYINT') !== false && $length == 1) {
+ $pgType = 'BOOLEAN';
+ } elseif (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) {
+ $parsedEnum = $this->parseEnumValue($length);
+ $pgType = 'CHARACTER VARYING(' . ($parsedEnum['maxLength'] + 2) . ')';
+ } elseif (($pgType === 'CHARACTER VARYING' || $pgType === 'CHARACTER' || $pgType === 'CHAR') && $length > 0) {
+ $pgType .= "($length)";
+ }
+
+ return $pgType;
+ }
+
+ /**
+ * Parses an ENUM type value and extracts the values in single quotes, also calculating the maximum length.
+ * @param string $inputString The ENUM values in a string format.
+ * @return array An associative array containing the result array and maximum length of ENUM values.
+ */
+ private function parseEnumValue($inputString) {
+ preg_match_all("/'([^']+)'/", $inputString, $matches);
+ $resultArray = $matches[1];
+ $maxLength = max(array_map('strlen', $resultArray));
+
+ return ['resultArray' => $resultArray, 'maxLength' => $maxLength];
+ }
+
+ /**
+ * Parses a numeric type value like DECIMAL(6,3), NUMERIC(10,2), etc.
+ * @param string $inputString The numeric value in string format, like 'DECIMAL(6, 3)'.
+ * @return array An associative array containing the type (e.g., DECIMAL) and the length (total digits) and scale (digits after the decimal point).
+ */
+ private function parseNumericType($inputString) {
+ preg_match_all("/([A-Za-z0-9_]+)/", $inputString, $matches); // NOSONAR
+ $resultArray = $matches[1];
+ $maxLength = max(array_map('strlen', $resultArray));
+
+ return ['resultArray' => $resultArray, 'maxLength' => $maxLength];
+ }
+
+ /**
+ * Converts the table schema into a SQL CREATE TABLE statement for the specified database.
+ *
+ * This method generates a SQL CREATE TABLE statement based on the provided table schema and target database type,
+ * adapting column definitions, default values, and other relevant attributes.
+ *
+ * @param array $table The table schema to convert.
+ * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+ * @return string The SQL CREATE TABLE statement.
+ */
+ public function toTable($table, $targetType) {
+ $tableName = $table['tableName'];
+
+ $lines = [];
+
+ // Fix table name if necessary
+ $tableName = $this->fixTableName($tableName, $targetType);
+
+ $lines[] = "CREATE TABLE IF NOT EXISTS $tableName (";
+ $linesCol = [];
+
+ foreach ($table['columns'] as $column) {
+ $columnName = $this->fixColumnName($column['Field'], $targetType);
+ $columnType = $column['Type'];
+ $primaryKey = ($column['Field'] === $table['primaryKey']);
+ $colDef = "\t$columnName $columnType";
+
+ // Handle primary key
+ if ($primaryKey) {
+ $colDef .= " PRIMARY KEY";
+ if ($this->isAutoIncrement($column['AutoIncrement'], $targetType)) {
+ $colDef .= " AUTO_INCREMENT";
+ }
+ $column['Nullable'] = false;
+ }
+ // Handle nullability
+ else if ($column['Nullable']) {
+ $colDef .= " NULL";
+ } else {
+ $colDef .= " NOT NULL";
+ }
+
+ // Handle default values
+ $defaultValue = $column['Default'];
+ if ($this->hasDefaultValue($primaryKey, $defaultValue)) {
+ $defaultValue = $this->replaceAll($defaultValue, '::character varying', '');
+ $defaultValue = $this->fixDefaultValue($defaultValue, $targetType);
+ if ($this->isNotEmpty($defaultValue)) {
+ $colDef .= $this->getDefaultData($defaultValue, $columnType);
+ }
+ }
+
+ // Handle column comments
+ if (isset($column['Comment'])) {
+ $colDef .= " COMMENT '" . $this->addslashes($column['Comment']) . "'";
+ }
+
+ $linesCol[] = $colDef;
+ }
+
+ $lines[] = implode(",\r\n", $linesCol);
+ $lines[] = ");";
+
+ return implode("\r\n", $lines);
+ }
+
+
+
+ /**
+ * Checks if the column type is TINYINT with a length of 1.
+ *
+ * This method checks if the given column type is 'TINYINT' and if its length
+ * is exactly 1, which is commonly used to represent boolean values in certain databases.
+ *
+ * @param string $type The data type of the column (e.g., 'TINYINT').
+ * @param int $length The length of the column (e.g., 1).
+ * @return bool True if the type is 'TINYINT' and length is 1, otherwise false.
+ */
+ public function isTinyInt1($type, $length) {
+ return strtoupper($type) === 'TINYINT' && $length == 1;
+ }
+
+ /**
+ * Checks if the column type is an integer (e.g., TINYINT, SMALLINT, INT, BIGINT).
+ *
+ * @param string $type The column data type (e.g., 'INT', 'BIGINT').
+ * @return bool True if the column type is an integer type, otherwise false.
+ */
+ public function isInteger($type) {
+ $type = strtoupper($type);
+ return $type === 'TINYINT' ||
+ $type === 'SMALLINT' ||
+ $type === 'MEDIUMINT' ||
+ $type === 'BIGINT' ||
+ $type === 'INTEGER' ||
+ $type === 'INT';
+ }
+
+ /**
+ * Determines if a column is auto-incremented for MySQL databases.
+ *
+ * @param bool $autoIncrement Whether the column is set to auto-increment.
+ * @param string $targetType The target database type (e.g., 'mysql', 'mariadb').
+ * @return bool True if the column is auto-incremented in MySQL or MariaDB, otherwise false.
+ */
+ public function isAutoIncrement($autoIncrement, $targetType) {
+ return $this->isMySQL($targetType) && $autoIncrement;
+ }
+
+ /**
+ * Checks if a value is not empty (not null or an empty string).
+ *
+ * @param string $value The value to check.
+ * @return bool True if the value is not empty, otherwise false.
+ */
+ public function isNotEmpty($value) {
+ return $value !== null && $value !== '';
+ }
+
+ /**
+ * Determines if a column has a default value, excluding primary keys.
+ *
+ * @param bool $primaryKey Whether the column is a primary key.
+ * @param string $defaultValue The default value of the column.
+ * @return bool True if the column has a default value, otherwise false.
+ */
+ public function hasDefaultValue($primaryKey, $defaultValue) {
+ return !$primaryKey && $defaultValue !== null && $defaultValue !== '';
+ }
+
+ /**
+ * Fixes the table name according to the target database type.
+ *
+ * This method adjusts the table name by removing any database prefix and applying
+ * the appropriate syntax for the target database (e.g., quoting for MySQL or PostgreSQL).
+ *
+ * @param string $tableName The name of the table to fix.
+ * @param string $targetType The target database type (e.g., 'mysql', 'pgsql').
+ * @return string The fixed table name.
+ */
+ public function fixTableName($tableName, $targetType) {
+ if (strpos($tableName, '.') !== false) {
+ $tableName = explode('.', $tableName)[1];
+ }
+
+ if ($this->isMySQL($targetType)) {
+ $tableName = '`' . $tableName . '`';
+ }
+ else if ($this->isPGSQL($targetType)) {
+ $tableName = '"' . $tableName . '"';
+ }
+
+ return $tableName;
+ }
+
+ /**
+ * Fixes the column name according to the target database type.
+ *
+ * This method applies proper quoting for column names based on the target database
+ * (e.g., MySQL uses backticks for column names).
+ *
+ * @param string $columnName The name of the column to fix.
+ * @param string $targetType The target database type (e.g., 'mysql').
+ * @return string The fixed column name.
+ */
+ public function fixColumnName($columnName, $targetType) {
+ if ($this->isMySQL($targetType)) {
+ $columnName = '`' . $columnName . '`';
+ }
+
+ return $columnName;
+ }
+
+ /**
+ * Generates the default value SQL for a column based on its type.
+ *
+ * This method returns the appropriate default value syntax for the column's type,
+ * handling different types such as BOOLEAN, INTEGER, and REAL.
+ *
+ * @param string $defaultValue The default value to apply to the column.
+ * @param string $columnType The type of the column (e.g., 'BOOLEAN', 'INT').
+ * @return string The default value SQL definition for the column.
+ */
+ public function getDefaultData($defaultValue, $columnType) {
+ $colDef = "";
+
+ if (strtoupper($defaultValue) == 'NULL') {
+ $colDef .= ' DEFAULT NULL'; // NOSONAR
+ }
+ else if ($this->isBoolean($columnType)) {
+ $colDef .= ' DEFAULT ' . $this->convertToBoolean($defaultValue); // NOSONAR
+ }
+ else if (strpos(strtoupper($columnType), 'INT') !== false) {
+ $colDef .= ' DEFAULT ' . $this->convertToInteger($defaultValue); // NOSONAR
+ }
+ else if ($this->isReal($columnType)) {
+ $colDef .= ' DEFAULT ' . $this->convertToReal($defaultValue); // NOSONAR
+ }
+ else {
+ $colDef .= ' DEFAULT ' . $defaultValue;
+ }
+
+ return $colDef;
+ }
+
+ /**
+ * Converts a value to a boolean format.
+ *
+ * @param mixed $value The value to convert.
+ * @return string The converted boolean value ('TRUE' or 'FALSE').
+ */
+ public function convertToBoolean($value) {
+ return (strtolower($value) == 'true' || $value === 1) ? 'TRUE' : 'FALSE';
+ }
+
+ /**
+ * Converts a value to an integer format.
+ *
+ * @param mixed $value The value to convert.
+ * @return int The converted integer value.
+ */
+ public function convertToInteger($value) {
+ return (int) $value;
+ }
+
+ /**
+ * Converts a value to a real number format.
+ *
+ * @param mixed $value The value to convert.
+ * @return float The converted real number value.
+ */
+ public function convertToReal($value) {
+ return (float) $value;
+ }
+
+ /**
+ * Fixes default value for SQLite.
+ *
+ * @param string $defaultValue The default value to fix.
+ * @param string $targetType The target database type.
+ * @return string The fixed default value.
+ */
+ public function fixDefaultValue($defaultValue, $targetType) {
+ if ($this->isSQLite($targetType) && stripos($defaultValue, 'now(') !== false) {
+ $defaultValue = '';
+
+ }
+ return $defaultValue;
+ }
+
+ /**
+ * Escapes special characters in a string for use in SQL statements.
+ *
+ * This method wraps the PHP `addslashes()` function, which adds backslashes before characters
+ * that need to be escaped in SQL, such as quotes, backslashes, and NULL characters.
+ *
+ * @param string $text The input string to escape.
+ * @return string The escaped string with special characters properly handled.
+ */
+ public function addslashes($text)
+ {
+ return addslashes($text);
+ }
+
+ /**
+ * Extracts the DROP TABLE IF EXISTS queries from the provided SQL string.
+ *
+ * @param string $sql The SQL string to be processed.
+ * @param string $targetType The type of database ('pgsql', 'mysql', or 'mariadb') to format the table names accordingly.
+ * @return array An array of objects, each containing the name of a table to be dropped.
+ */
+ public function extractDropTableQueries($sql, $targetType) {
+
+ // Remove backticks (`) from the entire SQL string before processing
+ $sqlWithoutBackticks = str_replace('`', '', $sql);
+ $result = [];
+ try
+ {
+ // Regular expression to capture DROP TABLE IF EXISTS command
+ $regex = '/DROP\s+TABLE\s+IF\s+EXISTS\s+([^\s]+)/i';
+ preg_match_all($regex, $sqlWithoutBackticks, $matches);
+
+ // Loop through all matches found
+ foreach ($matches[1] as $match) {
+ // Store the result in the desired format
+ $tableName = $this->extractTableName($match);
+
+ // Format the table name based on the target database type
+ if ($this->isPGSQL($targetType)) {
+ $tableName = '"' . $tableName . '"';
+ } else if ($this->isMySQL($targetType)) {
+ $tableName = '`' . $tableName . '`';
+ }
+ $result[] = [
+ 'table' => $tableName // Table name
+ ];
+ }
+ }
+ catch(Exception $e)
+ {
+ // Do nothing
+ }
+
+ return $result;
+ }
+
+ /**
+ * Extracts the table name from the input string, removing schema if present.
+ *
+ * @param string $input The input string (may contain schema.table or just table).
+ * @return string The extracted table name without schema.
+ */
+ public function extractTableName($input) {
+ // Check if the input contains a dot (indicating a schema)
+ if (strpos($input, '.') !== false) {
+ // If there is a dot, take the part after the dot as the table name
+ $input = explode('.', $input)[1];
+ }
+ // If there is no dot, it means the input is just the table name
+ return preg_replace('/[^a-zA-Z0-9_]/', '', $input); // NOSONAR
+ }
+
+
+}
diff --git a/src/Util/Database/PicoTableParser.php b/src/Util/Database/PicoTableParser.php
new file mode 100644
index 00000000..bd0287ab
--- /dev/null
+++ b/src/Util/Database/PicoTableParser.php
@@ -0,0 +1,414 @@
+getResult();
+ *
+ * The returned result contains structured information about each table, including
+ * columns, their types, constraints, and additional attributes.
+ *
+ * @package MagicObject\Util\Database
+ */
+class PicoTableParser {
+
+ /**
+ * List of valid SQL data types used for column validation.
+ *
+ * @var array
+ */
+ private $typeList = array();
+
+ /**
+ * Holds information about the parsed tables, including columns and their properties.
+ *
+ * @var array
+ */
+ private $tableInfo = array();
+
+ /**
+ * PicoTableParser constructor.
+ *
+ * Initializes the PicoTableParser instance and optionally parses an SQL string.
+ *
+ * @param string|null $sql Optional SQL string to parse during initialization.
+ */
+ public function __construct($sql = null) {
+ $this->init();
+
+ if ($sql) {
+ $this->parseAll($sql);
+ }
+ }
+
+ /**
+ * Initializes the type list for valid SQL column types.
+ *
+ * This function sets up an array of valid SQL column types, which will be used
+ * to validate column data types during parsing.
+ */
+ private function init() {
+ $typeList = 'TIMESTAMPTZ,TIMESTAMP,SERIAL4,BIGSERIAL,INT2,INT4,INT8,TINYINT,BIGINT,LONGTEXT,MEDIUMTEXT,TEXT,NVARCHAR,VARCHAR,ENUM,SET,NUMERIC,DECIMAL,CHAR,REAL,FLOAT,INTEGER,INT,DATETIME,DATE,DOUBLE,BOOLEAN,BOOL,TIME,UUID,MONEY,BLOB,BIT,JSON';
+ $this->typeList = explode(',', $typeList);
+ }
+
+ /**
+ * Checks if a value exists in an array.
+ *
+ * @param array $haystack The array to search.
+ * @param string $needle The value to search for.
+ * @return bool Returns true if the needle is found in the haystack, otherwise false.
+ */
+ private function inArray($haystack, $needle) {
+ return in_array($needle, $haystack);
+ }
+
+ /**
+ * Checks if a field is a primary key.
+ *
+ * @param string $field The field definition.
+ * @return bool True if the field is a primary key, otherwise false.
+ */
+ private function isPrimaryKey($field) {
+ $f = strtoupper(trim(preg_replace('/\s+/', ' ', $field))); // NOSONAR
+ return strpos($f, 'PRIMARY KEY') !== false;
+ }
+
+ /**
+ * Checks if a field is auto-incremented.
+ *
+ * @param string $line The field definition.
+ * @return bool True if the field is auto-incremented, otherwise false.
+ */
+ private function isAutoIncrement($line) {
+ $f = strtoupper(trim(preg_replace('/\s+/', ' ', $line)));
+ return strpos($f, 'AUTO_INCREMENT') !== false ||
+ strpos($f, 'SERIAL') !== false ||
+ strpos($f, 'BIGSERIAL') !== false ||
+ strpos($f, 'NEXTVAL') !== false;
+ }
+
+ /**
+ * Parses a CREATE TABLE SQL statement and extracts table and column information.
+ *
+ * @param string $sql The SQL string representing a CREATE TABLE statement.
+ * @return array An array containing table name, columns, and primary key information.
+ */
+ public function parseTable($sql) // NOSONAR
+ {
+ $rg_tb = '/(create\s+table\s+if\s+not\s+exists|create\s+table)\s(?.*)\s\(/i';
+ $rg_fld = '/(\w+\s+key.*|\w+\s+bigserial|\w+\s+serial4|\w+\s+serial8|\w+\s+tinyint.*|\w+\s+bigint.*|\w+\s+longtext.*|\w+\s+mediumtext.*|\w+\s+text.*|\w+\s+nvarchar.*|\w+\s+varchar.*|\w+\s+char.*|\w+\s+real.*|\w+\s+float.*|\w+\s+integer.*|\w+\s+int.*|\w+\s+datetime.*|\w+\s+date.*|\w+\s+double.*|\w+\s+timestamp.*|\w+\s+timestamptz.*|\w+\s+boolean.*|\w+\s+bool.*|\w+\s+enum\s*\(.*\)|\w+\s+set\s*\(.*\)|\w+\s+numeric\s*\(.*\)|\w+\s+decimal\s*\(.*\)|\w+\s+int2.*|\w+\s+int4.*|\w+\s+int8.*|\w+\s+time.*|\w+\s+uuid.*|\w+\s+money.*|\w+\s+blob.*|\w+\s+bit.*|\w+\s+json.*)/i'; // NOSONAR
+ $rg_fld2 = '/(?\w+)\s+(?\w+)(?.*)/i';
+ $rg_enum = '/enum\s*\(([^)]+)\)/i';
+ $rg_set = '/set\s*\(([^)]+)\)/i';
+ $rg_not_null = '/not\s+null/i';
+ $rg_pk = '/primary\s+key/i';
+ $rg_fld_def = '/default\s+([^\'"]+|\'[^\']*\'|\"[^\"]*\")\s*(comment\s+\'[^\']*\')?/i';
+ $rg_fld_comment = '/COMMENT\s*\'([^\']*)\'/i';
+ $rg_pk2 = '/(PRIMARY|UNIQUE) KEY[a-zA-Z_0-9\s]+\(([a-zA-Z_0-9,\s]+)\)/i'; // NOSONAR
+
+ preg_match($rg_tb, $sql, $result);
+ $tableName = $result['tb'];
+
+ $fieldList = [];
+ $primaryKey = null;
+ $columnList = [];
+ $primaryKeyList = [];
+
+ preg_match_all($rg_fld, $sql, $matches);
+
+ foreach ($matches[0] as $f) {
+ $line = $f;
+ preg_match($rg_fld2, $f, $fld_def);
+ $dataTypeRaw = $fld_def[0];
+ $dataType = $fld_def['ftype'];
+ $dataTypeOriginal = $dataType;
+ $isPk = false;
+ $enumValues = null;
+ $enumArray = null;
+
+ if (preg_match($rg_enum, $dataTypeRaw, $matches)) {
+ $enumValues = $matches[1];
+ $enumArray = array_map('trim', explode(',', $enumValues));
+ }
+ if (preg_match($rg_set, $dataTypeRaw, $matches)) {
+ $enumValues = $matches[1];
+ $enumArray = array_map('trim', explode(',', $enumValues));
+ }
+
+ if ($this->isValidType($dataType) || $this->isValidType($dataTypeOriginal)) {
+ $attr = trim(str_replace(',', '', $fld_def['fattr']));
+ $nullable = !preg_match($rg_not_null, $attr);
+ $attr2 = str_replace($rg_not_null, '', $attr);
+
+ $isPk = preg_match($rg_pk, $attr2) || $this->isPrimaryKey($line);
+ $isAi = $this->isAutoIncrement($line);
+
+ preg_match($rg_fld_def, $attr2, $def);
+ $defaultValue = isset($def[1]) ? trim($def[1]) : null;
+ $defaultValue = $this->fixDefaultValue($defaultValue);
+
+ preg_match($rg_fld_comment, $attr2, $cmn);
+ $comment = isset($cmn[1]) ? trim($cmn[1]) : null;
+
+ $dataType = trim($dataType);
+ $length = $this->getLength($attr);
+
+ $columnName = trim($fld_def['fname']);
+ if ($isPk)
+ {
+ $primaryKeyList[] = $columnName;
+ }
+ if (!in_array($columnName, $columnList)) {
+ $fieldList[] = [
+ 'Field' => $columnName,
+ 'Type' => $dataType,
+ 'Length' => $length,
+ 'Key' => $isPk,
+ 'Nullable' => $nullable,
+ 'Default' => $defaultValue,
+ 'AutoIncrement' => $isAi,
+ 'EnumValues' => $enumArray,
+ 'Comment' => $comment
+ ];
+ $columnList[] = $columnName;
+ }
+ } else if ($this->isPrimaryKey($line)) {
+ preg_match('/\((.*)\)/', $f, $matches);
+ if ($primaryKey == null) {
+ $primaryKey = isset($matches[1]) ? $matches[1] : null;
+ }
+ }
+
+ if ($primaryKey != null) {
+ $primaryKey = str_replace(['(', ')'], '', $primaryKey);
+ foreach ($fieldList as &$column) // NOSONAR
+ {
+ if ($column['Field'] == $primaryKey) {
+ $column['Key'] = true;
+ }
+ }
+ }
+
+ if (preg_match($rg_pk2, $f) && preg_match($rg_pk, $f)) {
+ $x = str_replace(preg_match($rg_pk, $f)[0], '', $f);
+ $x = str_replace(['(', ')'], '', $x);
+ $pkeys = array_map('trim', explode(',', $x));
+ foreach ($fieldList as &$column) {
+ if (in_array($column['Field'], $pkeys)) {
+ $column['Key'] = true;
+ }
+ }
+ }
+ }
+
+ if ($primaryKey == null) {
+ $primaryKey = $primaryKeyList[0] ?? null;
+ }
+
+ return ['tableName' => $tableName, 'columns' => $fieldList, 'primaryKey' => $primaryKey];
+ }
+
+ /**
+ * Fixes and normalizes default values in SQL statements.
+ *
+ * @param string $defaultValue The raw default value from SQL.
+ * @return string|null The normalized default value or null if empty.
+ */
+ private function fixDefaultValue($defaultValue) {
+ $defaultValue = trim($defaultValue);
+ return empty($defaultValue) || $defaultValue == 'null' ? null : $defaultValue;
+ }
+
+ /**
+ * Validates if a type is one of the known types.
+ *
+ * @param string $type The data type to validate.
+ * @return bool True if the type is valid, otherwise false.
+ */
+ private function isValidType($type) {
+ return $this->inArray($this->typeList, $type);
+ }
+
+ /**
+ * Extracts the length for types that require it, such as varchar.
+ *
+ * @param string $attr The attributes of the field.
+ * @return int|null The length of the field or null if not applicable.
+ */
+ private function getLength($attr) {
+ preg_match('/\((\d+)\)/', $attr, $matches);
+ return isset($matches[1]) ? (int) trim($matches[1], '()') : null;
+ }
+
+ /**
+ * Parses multiple SQL statements.
+ *
+ * @param string $sql The SQL statements.
+ */
+ public function parseAll($sql) {
+
+ $tables = $this->parseSQL($sql);
+
+ foreach ($tables as $table) {
+ if (isset($table['query'])) {
+ try
+ {
+ $info = $this->parseTable($table['query']);
+
+ $this->tableInfo[] = $info;
+ }
+ catch(Exception $e)
+ {
+ // Do nothing
+ }
+ }
+ }
+ }
+
+ /**
+ * Parses a SQL text, splits it into individual SQL queries, and processes them by handling
+ * delimiters, comments, and empty lines. It returns an array of queries, each with its delimiter.
+ *
+ * This method processes the SQL text by:
+ * 1. Normalizing newlines.
+ * 2. Removing comment lines and empty lines.
+ * 3. Splitting the SQL text into queries based on the delimiter.
+ * 4. Handling multiple queries that may span across multiple lines.
+ * 5. Changing the delimiter if a new one is specified within the SQL text.
+ *
+ * @param string $sqlText The input SQL text to be parsed.
+ *
+ * @return array An array of queries, each containing the SQL query string and its corresponding delimiter.
+ * Each item in the array is an associative array with two keys:
+ * - 'query': the SQL query string.
+ * - 'delimiter': the delimiter used for that query (e.g., ";").
+ */
+ public function parseSQL($sqlText) // NOSONAR
+ {
+ $sqlText = str_replace("\n", "\r\n", $sqlText);
+ $sqlText = str_replace("\r\r\n", "\r\n", $sqlText);
+ $arr = explode("\r\n", $sqlText);
+ $arr2 = array();
+ foreach($arr as $key=>$val)
+ {
+ $arr[$key] = ltrim($val);
+ if (stripos($arr[$key], "-- ") !== 0 && $arr[$key] !== "--" && $arr[$key] !== "") {
+ $arr2[] = $arr[$key];
+ }
+ }
+ $arr = $arr2;
+ unset($arr2);
+
+ $append = 0;
+ $skip = 0;
+ $start = 1;
+ $nquery = -1;
+ $delimiter = ";";
+ $query_array = array();
+ $delimiter_array = array();
+
+ foreach($arr as $line=>$text)
+ {
+ if($text == "" && $append == 1)
+ {
+ $query_array[$nquery] .= "\r\n";
+ }
+ if($append == 0)
+ {
+ if(stripos(ltrim($text, " \t "), "--") === 0)
+ {
+ $skip = 1;
+ $nquery++;
+ $start = 1;
+ $append = 0;
+ }
+ else
+ {
+ $skip = 0;
+ }
+ }
+ if($skip == 0)
+ {
+ if($start == 1)
+ {
+ $nquery++;
+ $query_array[$nquery] = "";
+ $delimiter_array[$nquery] = $delimiter;
+ $start = 0;
+ }
+ $query_array[$nquery] .= $text."\r\n";
+ $delimiter_array[$nquery] = $delimiter;
+ $text = ltrim($text, " \t ");
+ $start = strlen($text)-strlen($delimiter)-1;
+ if(stripos(substr($text, $start), $delimiter) !== false || $text == $delimiter)
+ {
+ $nquery++;
+ $start = 1;
+ $append = 0;
+ }
+ else
+ {
+ $start = 0;
+ $append = 1;
+ }
+ $delimiter_array[$nquery] = $delimiter;
+ if(stripos($text, "delimiter ") !== false)
+ {
+ $text = trim(preg_replace("/\s+/"," ",$text));
+ $arr2 = explode(" ", $text);
+ $delimiter = $arr2[1];
+ $nquery++;
+ $delimiter_array[$nquery] = $delimiter;
+ $start = 1;
+ $append = 0;
+ }
+ }
+ }
+ $result = array();
+ foreach($query_array as $line=>$sql)
+ {
+ $delimiter = $delimiter_array[$line];
+ if (stripos($sql, "delimiter ") !== 0) {
+ $sql = rtrim($sql, " \r\n\t ");
+ $sql = substr($sql, 0, strlen($sql) - strlen($delimiter));
+ $result[] = array("query" => $sql, "delimiter" => $delimiter);
+ }
+ }
+ return $result;
+ }
+
+ /**
+ * Returns the table information parsed from the SQL statements.
+ *
+ * @return array An array of parsed table information.
+ */
+ public function getTableInfo() {
+ return $this->tableInfo;
+ }
+
+ /**
+ * Get the value of tableInfo.
+ *
+ * @return array The table information.
+ */
+ public function getResult()
+ {
+ return $this->getTableInfo();
+ }
+}
diff --git a/tutorial.md b/tutorial.md
index d9f6186c..b3e1bb02 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -2294,16 +2294,37 @@ Session variables keep information about one single user, and are available to a
```yaml
session:
- name: MUSICPRODUCTIONMANAGER
- max_life_time: 86400
- save_handler: files
- save_path: /tmp/sessions
+ name: ${SESSION_NAME}
+ max_life_time: ${SESSION_MAX_LIFE_TIME}
+ save_handler: ${SESSION_SAVE_HANDLER}
+ save_path: ${SESSION_SAVE_PATH}
+ cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+ cookie_path: ${SESSION_COOKIE_PATH}
+ cookie_secure: ${SESSION_COOKIE_SECURE}
+ cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+ cookie_domain: ${SESSION_COOKIE_DOMAIN}
```
-- `name`: Name of the session.
-- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours).
-- `save_handler`: Specifies the session storage mechanism (in this case, files).
-- `save_path`: Directory where session files are stored.
+### Explanation:
+
+- **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier.
+
+- **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires.
+
+- **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration.
+
+- **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler).
+
+- **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration.
+
+- **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set.
+
+- **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections.
+
+- **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie.
+
+- **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session.
+
**PHP Script**
@@ -2354,10 +2375,15 @@ For example:
```yaml
session:
- name: MUSICPRODUCTIONMANAGER
- max_life_time: 86400
- save_handler: redis
+ name: ${SESSION_NAME}
+ max_life_time: ${SESSION_MAX_LIFE_TIME}
+ save_handler: ${SESSION_SAVE_HANDLER}
save_path: ${SESSION_SAVE_PATH}
+ cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+ cookie_path: ${SESSION_COOKIE_PATH}
+ cookie_secure: ${SESSION_COOKIE_SECURE}
+ cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+ cookie_domain: ${SESSION_COOKIE_DOMAIN}
```
`${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key.