module_name = $module_name; $this->config_variable = $config_variable; $this->config_variable_type = $config_variable_type; $this->config_variable_required = $config_variable_required; $this->allowed_values = $allowed_values; $this->config_variable_description = $config_variable_description; $this->config_variable_example = $config_variable_example; $this->config_variable_is_secret = $config_variable_is_secret; // Check if the config variable is set in the database if (!self::isVariableSet($module_name, $config_variable)) { // If the config variable is required, throw an exception if ($config_variable_required && $default_value === null) { throw new Exception('Config variable not set: ' . $config_variable); } else { // If the config variable is not required, set the default value self::insertVariableValue($module_name, $config_variable, $default_value); } } } /** * Check if the config variable is set in the database * @param string $module * @param string $variable * @return bool */ static function isVariableSet(string $module, string $variable): bool { $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'"; $result = $db->query($sql); return $result->num_rows > 0; } /** * Insert the value of the config variable in the database * @param string $module * @param string $variable * @param mixed $value */ function insertVariableValue(string $module, string $variable, mixed $value): void { $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $value = $db->escape_string((string)$value); $type = $db->escape_string(self::getVariableType()); $sql = "INSERT INTO module_config (module, variable, value, type) VALUES ('$module', '$variable', '$value', '$type')"; $db->query($sql); system_search_cache::markDirtyTable('module_config'); } /** * Get the variable type * @return string */ function getVariableType(): string { return $this->config_variable_type; } /** * Is boolean variable true? * @return bool * @throws Exception */ function isTrue(): bool { // Make sure the variable is a boolean if ($this->config_variable_type !== 'bool') { throw new Exception('Config variable is not a boolean: ' . $this->config_variable . ' (' . $this->config_variable_type . ')'); } return $this->getVariableValue() === 'true'; } /** * Get the value of the config variable from the database * @return mixed */ function getVariableValue(): mixed { $db = self::getModuleConfigDatabase(); $module = $db->escape_string($this->module_name); $variable = $db->escape_string($this->config_variable); $sql = "SELECT value FROM module_config WHERE module = '$module' AND variable = '$variable'"; $result = $db->query($sql); // return the value of the config variable return $result->fetch_assoc()['value']; } public function __toString(): string { // Return the config variable value as a string return $this->getVariableValue(); } /** * Set the value of the config variable in the database * @param mixed $value * @throws Exception */ function setVariableValue(mixed $value): void { // Check if the variable value is valid if (!$this->validateVariableValue($value)) { throw new Exception('Invalid value for config variable: ' . $this->config_variable . ' (' . $value . ')'); } // If the type is a boolean, convert the value to "true" or "false" if ($this->config_variable_type === 'bool') { $value = $value ? 'true' : 'false'; } // Update the value of the config variable in the database, and insert it if it does not exist if (self::isVariableSet($this->module_name, $this->config_variable)) { self::updateVariableValue($this->module_name, $this->config_variable, $value); } else { self::insertVariableValue($this->module_name, $this->config_variable, $value); } } /** * Validate the value of the config variable * @param mixed $value * @return bool */ function validateVariableValue(mixed $value): bool { if ($value === null) { return !$this->config_variable_required; } if ($value === '' && !$this->config_variable_required && $this->config_variable_type === 'int') { return true; } // Check if the value is empty and the variable is required if ($this->config_variable_required && empty($value)) { // If the value is empty and the type is not a boolean, return false if ($this->config_variable_type != 'bool') { return false; } } // Check if the value is in the list of allowed values if ($this->allowed_values && !in_array($value, $this->allowed_values)) { return false; } // Check if the value is of the correct type switch ($this->config_variable_type) { case 'int': if (!is_numeric($value)) { return false; } break; case 'string': if (!is_string($value)) { return false; } break; case 'bool': // If the value is a string, convert it to "true" or "false" if (is_string($value)) { $value = self::inputToBool($value); } if (!is_bool($value)) { return false; } break; default: return false; } return true; } /** * Convert a string to a boolean * @param string $value * @return bool */ static function inputToBool(string $value): bool { // If the value is true, 1, or "true", return true if ($value === 'true' || $value === '1') { return true; } // If the value is false, 0, or "false", return false return false; } /** * Update the value of the config variable in the database * @param string $module * @param string $variable * @param mixed $value */ static function updateVariableValue(string $module, string $variable, mixed $value): void { $db = self::getModuleConfigDatabase(); $module = $db->escape_string($module); $variable = $db->escape_string($variable); $value = $db->escape_string((string)$value); $sql = "UPDATE module_config SET value = '$value' WHERE module = '$module' AND variable = '$variable'"; $db->query($sql); system_search_cache::markDirtyTable('module_config'); } private static function getModuleConfigDatabase(): \classes\db { global $db, $CONFIG_DB; if ($db instanceof \classes\db) { return $db; } if (!is_array($CONFIG_DB) || $CONFIG_DB === []) { $CONFIG_DB = self::readDatabaseConfigFromEnvironment(); } if (!is_array($CONFIG_DB) || $CONFIG_DB === []) { throw new Exception('Database configuration is not available'); } $db = new \classes\db($CONFIG_DB); $db->connect(); return $db; } private static function readDatabaseConfigFromEnvironment(): array { $readEnv = static function (string $key, string $default = ''): string { $value = $_ENV[$key] ?? $_SERVER[$key] ?? getenv($key); if ($value === false || $value === null) { return $default; } return trim((string)$value); }; $dbTarget = strtolower(trim($readEnv('CONFIG_DB_TARGET', 'live'))); if ($dbTarget !== 'live' && $dbTarget !== 'debug') { $dbTarget = 'live'; } $resolveDbValue = static function (string $key) use ($dbTarget, $readEnv): string { $liveValue = $readEnv('CONFIG_DB_' . $key); $debugValue = $readEnv('CONFIG_DB_DEBUG_' . $key); if ($dbTarget === 'debug' && $debugValue !== '') { return $debugValue; } return $liveValue; }; $host = $resolveDbValue('HOST'); $user = $resolveDbValue('USER'); $database = $resolveDbValue('DATABASE'); if ($host === '' || $user === '' || $database === '') { return []; } return [ 'host' => $host, 'user' => $user, 'password' => $resolveDbValue('PASSWORD'), 'database' => $database, 'port' => (int)($resolveDbValue('PORT') ?: '3306'), 'ssl_mode' => $resolveDbValue('SSL_MODE') ?: 'DISABLED', ]; } /** * Get the config variable as a JSON string * @return string */ function asJson(): string { return json_encode($this->asArray()); } /** * Get the config variable as an array * @return array */ function asArray(): array { return [ 'module' => $this->module_name, 'variable' => $this->config_variable, 'type' => $this->config_variable_type, 'required' => $this->config_variable_required, 'allowed_values' => $this->allowed_values, 'description' => $this->config_variable_description, 'example' => $this->config_variable_example, 'value' => $this->getVariableValue(), ]; } /** * Get the variable name * @return string */ function getVariableName(): string { return $this->config_variable; } /** * Get the variable description * @return string */ function getVariableDescription(): string { return $this->config_variable_description; } /** * Get the variable example * @return string */ function getVariableExample(): string { return $this->config_variable_example; } /** * Get the variable is secret * @return bool */ function getVariableIsSecret(): bool { return $this->config_variable_is_secret; } /** * Get the variable required * @return bool */ function getVariableRequired(): bool { return $this->config_variable_required; } }