fix: Handle empty database on fresh install (#4467)

* fix: Handle empty database on fresh install
* feat: Add migration progress bar with jQuery AJAX

- Session.php: Switch to file-based sessions when migrations table doesn't exist
- OSPOS.php: Catch DatabaseException when config table missing, set defaults
- MY_Migration.php: Handle database connection failures gracefully
- Load_config.php: Set default language settings when config empty
---------

Co-authored-by: Ollama <ollama@steganos.dev>
This commit is contained in:
jekkos
2026-04-08 20:19:25 +00:00
committed by GitHub
parent 71056d9b03
commit 7f9321eca0
59 changed files with 800 additions and 176 deletions

View File

@@ -1,3 +0,0 @@
FROM php:8.4-cli
RUN apt-get update && apt-get install -y libicu-dev && docker-php-ext-install intl
WORKDIR /app

View File

@@ -70,7 +70,7 @@ class Filters extends BaseFilters
public array $globals = [ public array $globals = [
'before' => [ 'before' => [
'honeypot', 'honeypot',
'csrf' => ['except' => 'login'], 'csrf' => ['except' => 'login|migrate'],
'invalidchars', 'invalidchars',
], ],
'after' => [ 'after' => [

View File

@@ -5,6 +5,7 @@ namespace Config;
use App\Models\Appconfig; use App\Models\Appconfig;
use CodeIgniter\Cache\CacheInterface; use CodeIgniter\Cache\CacheInterface;
use CodeIgniter\Config\BaseConfig; use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Database\Exceptions\DatabaseException;
/** /**
* This class holds the configuration options stored from the database so that on launch those settings can be cached * This class holds the configuration options stored from the database so that on launch those settings can be cached
@@ -34,11 +35,21 @@ class OSPOS extends BaseConfig
if ($cache) { if ($cache) {
$this->settings = decode_array($cache); $this->settings = decode_array($cache);
} else { } else {
$appconfig = model(Appconfig::class); try {
foreach ($appconfig->get_all()->getResult() as $app_config) { $appconfig = model(Appconfig::class);
$this->settings[$app_config->key] = $app_config->value; foreach ($appconfig->get_all()->getResult() as $app_config) {
$this->settings[$app_config->key] = $app_config->value;
}
$this->cache->save('settings', encode_array($this->settings));
} catch (DatabaseException $e) {
// Database table doesn't exist yet (migrations haven't run)
// Return empty settings to allow migration page to display
$this->settings = [
'language' => 'english',
'language_code' => 'en',
'company' => 'Home'
];
} }
$this->cache->save('settings', encode_array($this->settings));
} }
} }
@@ -50,4 +61,4 @@ class OSPOS extends BaseConfig
$this->cache->delete('settings'); $this->cache->delete('settings');
$this->set_settings(); $this->set_settings();
} }
} }

View File

@@ -10,6 +10,7 @@ $routes->setDefaultController('Login');
$routes->get('/', 'Login::index'); $routes->get('/', 'Login::index');
$routes->get('login', 'Login::index'); $routes->get('login', 'Login::index');
$routes->post('login', 'Login::index'); $routes->post('login', 'Login::index');
$routes->post('migrate', 'Login::migrate');
$routes->add('no_access/index/(:segment)', 'No_access::index/$1'); $routes->add('no_access/index/(:segment)', 'No_access::index/$1');
$routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2'); $routes->add('no_access/index/(:segment)/(:segment)', 'No_access::index/$1/$2');

View File

@@ -5,6 +5,8 @@ namespace Config;
use CodeIgniter\Config\BaseConfig; use CodeIgniter\Config\BaseConfig;
use CodeIgniter\Session\Handlers\BaseHandler; use CodeIgniter\Session\Handlers\BaseHandler;
use CodeIgniter\Session\Handlers\DatabaseHandler; use CodeIgniter\Session\Handlers\DatabaseHandler;
use CodeIgniter\Session\Handlers\FileHandler;
use Config\Database;
class Session extends BaseConfig class Session extends BaseConfig
{ {
@@ -124,4 +126,23 @@ class Session extends BaseConfig
* seconds. * seconds.
*/ */
public int $lockMaxRetries = 300; public int $lockMaxRetries = 300;
public function __construct()
{
parent::__construct();
if ($this->driver === DatabaseHandler::class) {
try {
$db = Database::connect();
if (!$db->tableExists($this->savePath)) {
$this->driver = FileHandler::class;
$this->savePath = WRITEPATH . 'session';
}
} catch (\CodeIgniter\Database\Exceptions\DatabaseException $e) {
$this->driver = FileHandler::class;
$this->savePath = WRITEPATH . 'session';
}
}
}
} }

View File

@@ -2,6 +2,7 @@
namespace App\Controllers; namespace App\Controllers;
use App\Libraries\MY_Migration;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface; use CodeIgniter\HTTP\ResponseInterface;
@@ -81,7 +82,7 @@ class Home extends Secure_Controller
if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) { if ($this->employee->check_password($this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), $this->request->getPost('current_password'))) {
// Validate password length BEFORE hashing // Validate password length BEFORE hashing
$new_password = $this->request->getPost('password'); $new_password = $this->request->getPost('password');
if (strlen($new_password) < 8) { if (strlen($new_password) < 8) {
return $this->response->setJSON([ return $this->response->setJSON([
'success' => false, 'success' => false,
@@ -89,7 +90,7 @@ class Home extends Secure_Controller
'id' => NEW_ENTRY 'id' => NEW_ENTRY
]); ]);
} }
$employee_data = [ $employee_data = [
'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS), 'username' => $this->request->getPost('username', FILTER_SANITIZE_FULL_SPECIAL_CHARS),
'password' => password_hash($new_password, PASSWORD_DEFAULT), 'password' => password_hash($new_password, PASSWORD_DEFAULT),
@@ -124,4 +125,4 @@ class Home extends Secure_Controller
]); ]);
} }
} }
} }

View File

@@ -5,6 +5,7 @@ namespace App\Controllers;
use App\Libraries\MY_Migration; use App\Libraries\MY_Migration;
use App\Models\Employee; use App\Models\Employee;
use CodeIgniter\HTTP\RedirectResponse; use CodeIgniter\HTTP\RedirectResponse;
use CodeIgniter\HTTP\ResponseInterface;
use CodeIgniter\Model; use CodeIgniter\Model;
use Config\OSPOS; use Config\OSPOS;
use Config\Services; use Config\Services;
@@ -36,6 +37,7 @@ class Login extends BaseController
$data = [ $data = [
'has_errors' => false, 'has_errors' => false,
'is_new_install' => !(MY_Migration::get_current_version()),
'is_latest' => $migration->is_latest(), 'is_latest' => $migration->is_latest(),
'latest_version' => $migration->get_latest_migration(), 'latest_version' => $migration->get_latest_migration(),
'gcaptcha_enabled' => $gcaptcha_enabled, 'gcaptcha_enabled' => $gcaptcha_enabled,
@@ -71,4 +73,28 @@ class Login extends BaseController
return redirect()->to('home'); return redirect()->to('home');
} }
public function migrate(): ResponseInterface
{
try {
$migration = new MY_Migration(config('Migrations'));
$migration->migrate_to_ci4();
set_time_limit(3600);
$migration->setNamespace('App')->latest();
return $this->response->setJSON([
'success' => true,
'message' => 'Migration completed successfully'
]);
} catch (\Exception $e) {
log_message('error', 'Migration failed: ' . $e->getMessage());
return $this->response->setJSON([
'success' => false,
'message' => 'Migration failed: ' . $e->getMessage()
])->setStatusCode(500);
}
}
} }

View File

@@ -21,63 +21,47 @@ class Load_config
{ {
public Session $session; public Session $session;
/**
* Loads configuration from database into App CI config and then applies those settings
*/
public function load_config(): void public function load_config(): void
{ {
// Migrations
$migration_config = config('Migrations'); $migration_config = config('Migrations');
$migration = new MY_Migration($migration_config); $migration = new MY_Migration($migration_config);
// Use file-based session until database is migrated $this->session = session();
$this->session = $this->createSession($migration->is_latest());
// Database Configuration
$config = config(OSPOS::class); $config = config(OSPOS::class);
if (!$migration->is_latest()) { if (!$migration->is_latest()) {
$this->session->destroy(); $this->session->destroy();
} }
// Language $this->setDefaultLanguage($config);
$language_exists = file_exists('../app/Language/' . current_language_code());
if (current_language_code() == null || current_language() == null || !$language_exists) { // TODO: current_language() is undefined
$config->settings['language'] = 'english';
$config->settings['language_code'] = 'en';
}
$language = Services::language(); $language = Services::language();
$language->setLocale($config->settings['language_code']); $language->setLocale(current_language_code());
// Time Zone
date_default_timezone_set($config->settings['timezone'] ?? ini_get('date.timezone')); date_default_timezone_set($config->settings['timezone'] ?? ini_get('date.timezone'));
bcscale(max(2, totals_decimals() + tax_decimals())); bcscale(max(2, totals_decimals() + tax_decimals()));
} }
/** private function setDefaultLanguage(OSPOS $config): void
* Creates session with appropriate handler.
* Uses file-based session until database is migrated, then switches to database session.
*
* This prevents a circular dependency where login requires session, but the sessions
* database table doesn't exist yet because migrations run after login.
*/
private function createSession(bool $isDbMigrated): Session
{ {
$sessionConfig = config('Session'); $languageCode = $config->settings['language_code'] ?? null;
// If database is not migrated and we're configured to use database sessions, if (empty($config->settings) || $languageCode === null) {
// temporarily fall back to file-based sessions to allow migrations to complete. $config->settings['language'] = 'english';
// Once migrations run, the user must re-authenticate (session is destroyed in $config->settings['language_code'] = 'en';
// load_config() when migrations are pending). return;
if (!$isDbMigrated && $sessionConfig->driver === DatabaseHandler::class) {
$sessionConfig = clone $sessionConfig;
$sessionConfig->driver = FileHandler::class;
$sessionConfig->savePath = WRITEPATH . 'session';
} }
return Services::session($sessionConfig); if (!$this->languageExists($languageCode)) {
$config->settings['language'] = 'english';
$config->settings['language_code'] = 'en';
}
}
private function languageExists(string $languageCode): bool
{
return file_exists(APPPATH . 'Language/' . $languageCode);
} }
} }

View File

@@ -22,9 +22,7 @@ function current_language_code(bool $load_system_language = false): string
} }
} }
$language_code = $config['language_code']; return $config->language_code ?? DEFAULT_LANGUAGE_CODE;
return empty($language_code) ? DEFAULT_LANGUAGE_CODE : $language_code;
} }
/** /**
@@ -45,9 +43,7 @@ function current_language(bool $load_system_language = false): string
} }
} }
$language = $config['language']; return $config->language ?? DEFAULT_LANGUAGE_CODE;
return empty($language) ? DEFAULT_LANGUAGE : $language;
} }
/** /**

View File

@@ -11,56 +11,54 @@ function check_encryption(): bool
$old_key = config('Encryption')->key; $old_key = config('Encryption')->key;
if ((empty($old_key)) || (strlen($old_key) < 64)) { if ((empty($old_key)) || (strlen($old_key) < 64)) {
// Create Key
$encryption = new Encryption(); $encryption = new Encryption();
$key = bin2hex($encryption->createKey()); $key = bin2hex($encryption->createKey());
config('Encryption')->key = $key; config('Encryption')->key = $key;
// Write to .env
$config_path = ROOTPATH . '.env'; $config_path = ROOTPATH . '.env';
$new_config_path = WRITEPATH . '/backup/.env';
$backup_path = WRITEPATH . '/backup/.env.bak'; $backup_path = WRITEPATH . '/backup/.env.bak';
$backup_folder = WRITEPATH . '/backup'; $backup_folder = WRITEPATH . '/backup';
if (!file_exists($backup_folder) && !mkdir($backup_folder)) { if (!file_exists($backup_folder)) {
log_message('error', 'Could not create backup folder'); @mkdir($backup_folder, 0750, true);
return false;
} }
if (!copy($config_path, $backup_path)) { if (!file_exists($config_path)) {
log_message('error', "Unable to copy $config_path to $backup_path"); $example_path = ROOTPATH . '.env.example';
if (file_exists($example_path)) {
@copy($example_path, $config_path);
} else {
@file_put_contents($config_path, "# OSPOS Configuration\n\n");
}
@chmod($config_path, 0640);
} }
// Copy to backup if (file_exists($config_path)) {
@chmod($config_path, 0660); @copy($config_path, $backup_path);
@chmod($backup_path, 0660); @chmod($backup_path, 0640);
@chmod($config_path, 0640);
$config_file = file_get_contents($config_path); $config_file = file_get_contents($config_path);
$config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
if (!empty($old_key)) { if (strpos($config_file, 'encryption.key') !== false) {
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n"; $config_file = preg_replace("/(encryption\.key.*=.*)('.*')/", "$1'$key'", $config_file);
$insertion_point = stripos($config_file, 'encryption.key'); } else {
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0); $config_file .= "\nencryption.key = '$key'\n";
}
if (!empty($old_key)) {
$old_line = "# encryption.key = '$old_key' REMOVE IF UNNEEDED\r\n";
$insertion_point = stripos($config_file, 'encryption.key');
if ($insertion_point !== false) {
$config_file = substr_replace($config_file, $old_line, $insertion_point, 0);
}
}
@file_put_contents($config_path, $config_file);
@chmod($config_path, 0640);
log_message('info', "Updated encryption key in $config_path");
} }
$handle = @fopen($config_path, 'w+');
if (empty($handle)) {
log_message('error', "Unable to open $config_path for updating");
return false;
}
@chmod($config_path, 0660);
$write_failed = !fwrite($handle, $config_file);
fclose($handle);
if ($write_failed) {
log_message('error', "Unable to write to $config_path for updating.");
return false;
}
log_message('info', "File $config_path has been updated.");
} }
return true; return true;
@@ -74,23 +72,14 @@ function abort_encryption_conversion(): void
$config_path = ROOTPATH . '.env'; $config_path = ROOTPATH . '.env';
$backup_path = WRITEPATH . '/backup/.env.bak'; $backup_path = WRITEPATH . '/backup/.env.bak';
$config_file = file_get_contents($backup_path); if (!file_exists($backup_path)) {
return;
$handle = @fopen($config_path, 'w+');
if (empty($handle)) {
log_message('error', "Unable to open $config_path to undo encryption conversion");
} else {
@chmod($config_path, 0660);
$write_failed = !fwrite($handle, $config_file);
fclose($handle);
if ($write_failed) {
log_message('error', "Unable to write to $config_path to undo encryption conversion.");
return;
}
log_message('info', "File $config_path has been updated to undo encryption conversion");
} }
@chmod($config_path, 0640);
$config_file = file_get_contents($backup_path);
@file_put_contents($config_path, $config_file);
log_message('info', "Restored $config_path from backup");
} }
/** /**
@@ -99,13 +88,10 @@ function abort_encryption_conversion(): void
function remove_backup(): void function remove_backup(): void
{ {
$backup_path = WRITEPATH . '/backup/.env.bak'; $backup_path = WRITEPATH . '/backup/.env.bak';
if (! file_exists($backup_path)) { if (!file_exists($backup_path)) {
return; return;
} }
if (!unlink($backup_path)) { @unlink($backup_path);
log_message('error', "Unable to remove $backup_path."); log_message('info', "Removed $backup_path");
return;
}
log_message('info', "File $backup_path has been removed");
} }

View File

@@ -9,6 +9,15 @@ return [
"login" => "دخول", "login" => "دخول",
"logout" => "تسجيل خروج", "logout" => "تسجيل خروج",
"migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.", "migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "كلمة السر", "password" => "كلمة السر",
"required_username" => "", "required_username" => "",
"username" => "اسم المستخدم", "username" => "اسم المستخدم",

View File

@@ -9,6 +9,15 @@ return [
"login" => "دخول", "login" => "دخول",
"logout" => "تسجيل خروج", "logout" => "تسجيل خروج",
"migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.", "migration_needed" => "سيبدأ ترحيل قاعدة البيانات إلى{0} بعد تسجيل الدخول.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "كلمة السر", "password" => "كلمة السر",
"required_username" => "خانة أسم المستخدم مطلوبة.", "required_username" => "خانة أسم المستخدم مطلوبة.",
"username" => "اسم المستخدم", "username" => "اسم المستخدم",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Giriş", "login" => "Giriş",
"logout" => "Çıxış", "logout" => "Çıxış",
"migration_needed" => "{0} -ə daxil olandan sonra verilənlər bazası miqrasiyası başlayacaq.", "migration_needed" => "{0} -ə daxil olandan sonra verilənlər bazası miqrasiyası başlayacaq.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Şifrə", "password" => "Şifrə",
"required_username" => "", "required_username" => "",
"username" => "İstifadəçi", "username" => "İstifadəçi",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Password", "password" => "Password",
"required_username" => "", "required_username" => "",
"username" => "Username", "username" => "Username",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Prijava", "login" => "Prijava",
"logout" => "Odjava", "logout" => "Odjava",
"migration_needed" => "Migracija baze podataka na {0} će početi nakon prijavljivanja.", "migration_needed" => "Migracija baze podataka na {0} će početi nakon prijavljivanja.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Lozinka", "password" => "Lozinka",
"required_username" => "", "required_username" => "",
"username" => "Korisničko ime", "username" => "Korisničko ime",

View File

@@ -9,6 +9,15 @@ return [
'login' => "چوونەژوورەوە", 'login' => "چوونەژوورەوە",
'logout' => "چوونەدەرەوە", 'logout' => "چوونەدەرەوە",
'migration_needed' => "گواستنەوەی داتابەیس بۆ {0} دوای چوونەژوورەوە دەست پێدەکات.", 'migration_needed' => "گواستنەوەی داتابەیس بۆ {0} دوای چوونەژوورەوە دەست پێدەکات.",
'migration_required' => "",
'migration_auth_message' => "",
'migration_initializing' => "",
'migration_running' => "",
'migration_complete' => "",
'migration_complete_login' => "",
'migration_failed' => "",
'migration_error_connection' => "",
'migration_complete_redirect' => "",
'password' => "وشەی نهێنی", 'password' => "وشەی نهێنی",
'required_username' => "خانەی ناوی بەکارهێنەر پێویستە.", 'required_username' => "خانەی ناوی بەکارهێنەر پێویستە.",
'username' => "ناوی بەکارهێنەر", 'username' => "ناوی بەکارهێنەر",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Heslo", "password" => "Heslo",
"required_username" => "", "required_username" => "",
"username" => "Uživatelské jméno", "username" => "Uživatelské jméno",

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -7,10 +7,19 @@ return [
"invalid_installation" => "", "invalid_installation" => "",
"invalid_username_and_password" => "Ungültiger Benutzername/Passwort", "invalid_username_and_password" => "Ungültiger Benutzername/Passwort",
"login" => "Login", "login" => "Login",
"logout" => "", "logout" => "Abmelden",
"migration_needed" => "", "migration_needed" => "Eine Datenbank-Migration auf {0} wird nach der Anmeldung gestartet.",
"migration_required" => "Datenbank-Migration erforderlich",
"migration_auth_message" => "Administrator-Anmeldedaten sind erforderlich, um die Datenbank-Migration auf Version {0} zu autorisieren. Bitte melden Sie sich an, um fortzufahren.",
"migration_initializing" => "Datenbank wird initialisiert",
"migration_running" => "Datenbank-Migrationen werden ausgeführt...",
"migration_complete" => "Datenbank erfolgreich initialisiert!",
"migration_complete_login" => "Sie können sich jetzt anmelden.",
"migration_failed" => "Migration fehlgeschlagen",
"migration_error_connection" => "Verbindungsfehler. Bitte versuchen Sie es erneut.",
"migration_complete_redirect" => "Migration abgeschlossen. Weiterleitung zur Anmeldung...",
"password" => "Passwort", "password" => "Passwort",
"required_username" => "", "required_username" => "Das Feld Benutzername ist erforderlich.",
"username" => "Benutzername", "username" => "Benutzername",
"welcome" => "", "welcome" => "Willkommen bei {0}!",
]; ];

View File

@@ -7,10 +7,19 @@ return [
"invalid_installation" => "Die Installation ist nicht korrekt, überprüfen Sie Ihre php.ini-Datei.", "invalid_installation" => "Die Installation ist nicht korrekt, überprüfen Sie Ihre php.ini-Datei.",
"invalid_username_and_password" => "Ungültiger Benutzername oder Passwort.", "invalid_username_and_password" => "Ungültiger Benutzername oder Passwort.",
"login" => "Login", "login" => "Login",
"logout" => "", "logout" => "Abmelden",
"migration_needed" => "", "migration_needed" => "Eine Datenbank-Migration auf {0} wird nach der Anmeldung gestartet.",
"migration_required" => "Datenbank-Migration erforderlich",
"migration_auth_message" => "Administrator-Anmeldedaten sind erforderlich, um die Datenbank-Migration auf Version {0} zu autorisieren. Bitte melden Sie sich an, um fortzufahren.",
"migration_initializing" => "Datenbank wird initialisiert",
"migration_running" => "Datenbank-Migrationen werden ausgeführt...",
"migration_complete" => "Datenbank erfolgreich initialisiert!",
"migration_complete_login" => "Sie können sich jetzt anmelden.",
"migration_failed" => "Migration fehlgeschlagen",
"migration_error_connection" => "Verbindungsfehler. Bitte versuchen Sie es erneut.",
"migration_complete_redirect" => "Migration abgeschlossen. Weiterleitung zur Anmeldung...",
"password" => "Passwort", "password" => "Passwort",
"required_username" => "", "required_username" => "Das Feld Benutzername ist erforderlich.",
"username" => "Benutzername", "username" => "Benutzername",
"welcome" => "", "welcome" => "Willkommen bei {0}!",
]; ];

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Logout", "logout" => "Logout",
"migration_needed" => "A database migration to {0} will start after login.", "migration_needed" => "A database migration to {0} will start after login.",
"migration_required" => "Database Migration Required",
"migration_auth_message" => "Administrator credentials are required to authorize the database migration to version {0}. Please login to proceed.",
"migration_initializing" => "Initializing Database",
"migration_running" => "Running database migrations...",
"migration_complete" => "Database initialized successfully!",
"migration_complete_login" => "You can now log in.",
"migration_failed" => "Migration failed",
"migration_error_connection" => "Connection error. Please try again.",
"migration_complete_redirect" => "Migration complete. Redirecting to login...",
"password" => "Password", "password" => "Password",
"required_username" => "The username field is required.", "required_username" => "The username field is required.",
"username" => "Username", "username" => "Username",

View File

@@ -11,6 +11,12 @@ return [
"migration_needed" => "A database migration to {0} will start after login.", "migration_needed" => "A database migration to {0} will start after login.",
"migration_required" => "Database Migration Required", "migration_required" => "Database Migration Required",
"migration_auth_message" => "Administrator credentials are required to authorize the database migration to version {0}. Please login to proceed.", "migration_auth_message" => "Administrator credentials are required to authorize the database migration to version {0}. Please login to proceed.",
"migration_initializing" => "Initializing Database",
"migration_running" => "Running database migrations...",
"migration_complete" => "Database initialized successfully!",
"migration_complete_login" => "You can now log in.",
"migration_failed" => "Migration failed",
"migration_error_connection" => "Connection error. Please try again.",
"migration_complete_redirect" => "Migration complete. Redirecting to login...", "migration_complete_redirect" => "Migration complete. Redirecting to login...",
"password" => "Password", "password" => "Password",
"required_username" => "The username field is required.", "required_username" => "The username field is required.",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Iniciar Sesión", "login" => "Iniciar Sesión",
"logout" => "Cerrar sesión", "logout" => "Cerrar sesión",
"migration_needed" => "La migración de la base de datos a {0} se iniciará después del inicio de sesión.", "migration_needed" => "La migración de la base de datos a {0} se iniciará después del inicio de sesión.",
"migration_required" => "Migración de base de datos requerida",
"migration_auth_message" => "Se requieren credenciales de administrador para autorizar la migración de la base de datos a la versión {0}. Inicie sesión para continuar.",
"migration_initializing" => "Inicializando base de datos",
"migration_running" => "Ejecutando migraciones de base de datos...",
"migration_complete" => "¡Base de datos inicializada correctamente!",
"migration_complete_login" => "Ahora puede iniciar sesión.",
"migration_failed" => "Migración fallida",
"migration_error_connection" => "Error de conexión. Por favor, inténtelo de nuevo.",
"migration_complete_redirect" => "Migración completada. Redirigiendo al inicio de sesión...",
"password" => "Contraseña", "password" => "Contraseña",
"required_username" => "El campo de nombre de usuario es obligatorio.", "required_username" => "El campo de nombre de usuario es obligatorio.",
"username" => "Usuario", "username" => "Usuario",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Salir", "logout" => "Salir",
"migration_needed" => "Una migración de base de datos a {0} empezara después de entrar.", "migration_needed" => "Una migración de base de datos a {0} empezara después de entrar.",
"migration_required" => "Migración de base de datos requerida",
"migration_auth_message" => "Se requieren credenciales de administrador para autorizar la migración de la base de datos a la versión {0}. Inicie sesión para continuar.",
"migration_initializing" => "Inicializando base de datos",
"migration_running" => "Ejecutando migraciones de base de datos...",
"migration_complete" => "¡Base de datos inicializada correctamente!",
"migration_complete_login" => "Ahora puede iniciar sesión.",
"migration_failed" => "Migración fallida",
"migration_error_connection" => "Error de conexión. Por favor, inténtelo de nuevo.",
"migration_complete_redirect" => "Migración completada. Redirigiendo al inicio de sesión...",
"password" => "Contraseña", "password" => "Contraseña",
"required_username" => "El nombre de usuario es obligatorio.", "required_username" => "El nombre de usuario es obligatorio.",
"username" => "Usuario", "username" => "Usuario",

View File

@@ -9,6 +9,15 @@ return [
"login" => "وارد شدن", "login" => "وارد شدن",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "کلمه عبور", "password" => "کلمه عبور",
"required_username" => "", "required_username" => "",
"username" => "نام کاربری", "username" => "نام کاربری",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Déconnexion", "logout" => "Déconnexion",
"migration_needed" => "Une migration de base de données vers {0} débutera après l'ouverture de session.", "migration_needed" => "Une migration de base de données vers {0} débutera après l'ouverture de session.",
"migration_required" => "Migration de base de données requise",
"migration_auth_message" => "Les identifiants administrateur sont requis pour autoriser la migration de la base de données vers la version {0}. Veuillez vous connecter pour continuer.",
"migration_initializing" => "Initialisation de la base de données",
"migration_running" => "Exécution des migrations de la base de données...",
"migration_complete" => "Base de données initialisée avec succès !",
"migration_complete_login" => "Vous pouvez maintenant vous connecter.",
"migration_failed" => "Échec de la migration",
"migration_error_connection" => "Erreur de connexion. Veuillez réessayer.",
"migration_complete_redirect" => "Migration terminée. Redirection vers la connexion...",
"password" => "Mot de passe", "password" => "Mot de passe",
"required_username" => "Le champ nom utilisateur est obligatoire.", "required_username" => "Le champ nom utilisateur est obligatoire.",
"username" => "Nom d'utilisateur", "username" => "Nom d'utilisateur",

View File

@@ -9,6 +9,15 @@ return [
"login" => "כניסה", "login" => "כניסה",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "סיסמה", "password" => "סיסמה",
"required_username" => "", "required_username" => "",
"username" => "שם משתמש", "username" => "שם משתמש",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Prijava", "login" => "Prijava",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Lozinka", "password" => "Lozinka",
"required_username" => "", "required_username" => "",
"username" => "Korisničko ime", "username" => "Korisničko ime",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Belépés", "login" => "Belépés",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Jelszó", "password" => "Jelszó",
"required_username" => "", "required_username" => "",
"username" => "Felhasználó név", "username" => "Felhasználó név",

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Masuk", "login" => "Masuk",
"logout" => "Keluar", "logout" => "Keluar",
"migration_needed" => "Migrasi basis data untuk {0} akan mulai setelah masuk.", "migration_needed" => "Migrasi basis data untuk {0} akan mulai setelah masuk.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Kata kunci", "password" => "Kata kunci",
"required_username" => "Kolom nama pengguna wajib diisi.", "required_username" => "Kolom nama pengguna wajib diisi.",
"username" => "Nama Anda", "username" => "Nama Anda",

View File

@@ -9,8 +9,17 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Uscita", "logout" => "Uscita",
"migration_needed" => "Dopo l'accesso verrà avviata una migrazione del database a {0}.", "migration_needed" => "Dopo l'accesso verrà avviata una migrazione del database a {0}.",
"migration_required" => "Migratione del database richiesta",
"migration_auth_message" => "Le credenziali di amministratore sono richieste per autorizzare la migrazione del database alla versione {0}. Effettua il login per continuare.",
"migration_initializing" => "Inizializzazione database",
"migration_running" => "Esecuzione migrazioni database...",
"migration_complete" => "Database inizializzato con successo!",
"migration_complete_login" => "È ora possibile effettuare il login.",
"migration_failed" => "Migrazione fallita",
"migration_error_connection" => "Errore di connessione. Riprova.",
"migration_complete_redirect" => "Migrazione completata. Reindirizzamento al login...",
"password" => "Password", "password" => "Password",
"required_username" => "", "required_username" => "Il campo nome utente è obbligatorio.",
"username" => "Username", "username" => "Username",
"welcome" => "Benvenuto in {0}!", "welcome" => "Benvenuto in {0}!",
]; ];

View File

@@ -9,6 +9,15 @@ return [
"login" => "ចូល", "login" => "ចូល",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "ពាក្យសំងាត់", "password" => "ពាក្យសំងាត់",
"required_username" => "", "required_username" => "",
"username" => "ឈ្មោះ", "username" => "ឈ្មោះ",

View File

@@ -9,6 +9,15 @@ return [
"login" => "ເຂົ້າລະບົບ", "login" => "ເຂົ້າລະບົບ",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Password", "password" => "Password",
"required_username" => "", "required_username" => "",
"username" => "Username", "username" => "Username",

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Log-uit", "logout" => "Log-uit",
"migration_needed" => "Een database migratie naar {0} zal starten na het inloggen.", "migration_needed" => "Een database migratie naar {0} zal starten na het inloggen.",
"migration_required" => "Database migratie vereist",
"migration_auth_message" => "Beheerdersreferenties zijn vereist om de databasemigratie naar versie {0} te autoriseren. Log in om verder te gaan.",
"migration_initializing" => "Database initialiseren",
"migration_running" => "Databasemigraties uitvoeren...",
"migration_complete" => "Database succesvol geïnitialiseerd!",
"migration_complete_login" => "U kunt nu inloggen.",
"migration_failed" => "Migratie mislukt",
"migration_error_connection" => "Verbindingsfout. Probeer het opnieuw.",
"migration_complete_redirect" => "Migratie voltooid. Doorsturen naar login...",
"password" => "Paswoord", "password" => "Paswoord",
"required_username" => "", "required_username" => "",
"username" => "Gebruiker", "username" => "Gebruiker",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Aanmelden", "login" => "Aanmelden",
"logout" => "Afmelden", "logout" => "Afmelden",
"migration_needed" => "Een databasemigratie naar {0} zal starten na aanmelding.", "migration_needed" => "Een databasemigratie naar {0} zal starten na aanmelding.",
"migration_required" => "Database migratie vereist",
"migration_auth_message" => "Beheerdersreferenties zijn vereist om de databasemigratie naar versie {0} te autoriseren. Log in om verder te gaan.",
"migration_initializing" => "Database initialiseren",
"migration_running" => "Databasemigraties uitvoeren...",
"migration_complete" => "Database succesvol geïnitialiseerd!",
"migration_complete_login" => "U kunt nu inloggen.",
"migration_failed" => "Migratie mislukt",
"migration_error_connection" => "Verbindingsfout. Probeer het opnieuw.",
"migration_complete_redirect" => "Migratie voltooid. Doorsturen naar login...",
"password" => "Wachtwoord", "password" => "Wachtwoord",
"required_username" => "Het gebruikersnaam veld is verplicht.", "required_username" => "Het gebruikersnaam veld is verplicht.",
"username" => "Gebruikersnaam", "username" => "Gebruikersnaam",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Zaloguj", "login" => "Zaloguj",
"logout" => "Wyloguj", "logout" => "Wyloguj",
"migration_needed" => "Migracja bazy danych do {0} zacznie się po zalogowaniu.", "migration_needed" => "Migracja bazy danych do {0} zacznie się po zalogowaniu.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Hasło", "password" => "Hasło",
"required_username" => "", "required_username" => "",
"username" => "Nazwa użytkownika", "username" => "Nazwa użytkownika",

View File

@@ -9,8 +9,17 @@ return [
'login' => "Autenticação", 'login' => "Autenticação",
'logout' => "Sair", 'logout' => "Sair",
'migration_needed' => "Uma migração do banco de dados para {0} será iniciada após o login.", 'migration_needed' => "Uma migração do banco de dados para {0} será iniciada após o login.",
'migration_required' => "Migração de banco de dados necessária",
'migration_auth_message' => "Credenciais de administrador são necessárias para autorizar a migração do banco de dados para a versão {0}. Faça login para continuar.",
'migration_initializing' => "Inicializando banco de dados",
'migration_running' => "Executando migrações do banco de dados...",
'migration_complete' => "Banco de dados inicializado com sucesso!",
'migration_complete_login' => "Agora você pode fazer login.",
'migration_failed' => "Migração falhou",
'migration_error_connection' => "Erro de conexão. Por favor, tente novamente.",
'migration_complete_redirect' => "Migração concluída. Redirecionando para login...",
'password' => "Senha", 'password' => "Senha",
'required_username' => "O campo nome de usuário é obrigatório.", 'required_username' => "O campo nome de usuário é obrigatório.",
'username' => "Usuário", 'username' => "Usuário",
'welcome' => "Bem-vindo", 'welcome' => "Bem-vindo ao {0}!",
]; ];

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Вход", "login" => "Вход",
"logout" => "Выход", "logout" => "Выход",
"migration_needed" => "Миграция базы данных в {0} начнется после входа в систему.", "migration_needed" => "Миграция базы данных в {0} начнется после входа в систему.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Пароль", "password" => "Пароль",
"required_username" => "", "required_username" => "",
"username" => "Имя пользователя", "username" => "Имя пользователя",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "Logga ut", "logout" => "Logga ut",
"migration_needed" => "En migration av databasen till {0} kommer att påbörjas efter inloggningen.", "migration_needed" => "En migration av databasen till {0} kommer att påbörjas efter inloggningen.",
"migration_required" => "Databasmigration krävs",
"migration_auth_message" => "Admin-uppgifter krävs för att godkänna databasmigrationen till version {0}. Logga in för att fortsätta.",
"migration_initializing" => "Initierar databas",
"migration_running" => "Kör databasmigrationer...",
"migration_complete" => "Databas initierad framgångsrikt!",
"migration_complete_login" => "Du kan nu logga in.",
"migration_failed" => "Migration misslyckades",
"migration_error_connection" => "Anslutningsfel. Försök igen.",
"migration_complete_redirect" => "Migration klar. Omdirigerar till login...",
"password" => "Lösenord", "password" => "Lösenord",
"required_username" => "Fältet för användarnamn krävs.", "required_username" => "Fältet för användarnamn krävs.",
"username" => "Användarnamn", "username" => "Användarnamn",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Ingia", "login" => "Ingia",
"logout" => "Toka", "logout" => "Toka",
"migration_needed" => "Uhamishaji wa kanzidata hadi {0} utaanza baada ya kuingia.", "migration_needed" => "Uhamishaji wa kanzidata hadi {0} utaanza baada ya kuingia.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Nenosiri", "password" => "Nenosiri",
"required_username" => "Jina la mtumiaji ni lazima.", "required_username" => "Jina la mtumiaji ni lazima.",
"username" => "Jina la Mtumiaji", "username" => "Jina la Mtumiaji",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Ingia", "login" => "Ingia",
"logout" => "Toka", "logout" => "Toka",
"migration_needed" => "Uhamishaji wa kanzidata hadi {0} utaanza baada ya kuingia.", "migration_needed" => "Uhamishaji wa kanzidata hadi {0} utaanza baada ya kuingia.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Nenosiri", "password" => "Nenosiri",
"required_username" => "Jina la mtumiaji ni lazima.", "required_username" => "Jina la mtumiaji ni lazima.",
"username" => "Jina la Mtumiaji", "username" => "Jina la Mtumiaji",

View File

@@ -9,6 +9,15 @@ return [
"login" => "உள்நுழைய", "login" => "உள்நுழைய",
"logout" => "விடுபதிகை", "logout" => "விடுபதிகை",
"migration_needed" => "{0} க்கு தரவுத்தள இடம்பெயர்வு உள்நுழைந்த பிறகு தொடங்கும்.", "migration_needed" => "{0} க்கு தரவுத்தள இடம்பெயர்வு உள்நுழைந்த பிறகு தொடங்கும்.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "கடவுச்சொல்", "password" => "கடவுச்சொல்",
"required_username" => "", "required_username" => "",
"username" => "பயனர்பெயர்", "username" => "பயனர்பெயர்",

View File

@@ -9,6 +9,15 @@ return [
"login" => "ลงชื่อเข้าใช้", "login" => "ลงชื่อเข้าใช้",
"logout" => "ออกจากระบบ", "logout" => "ออกจากระบบ",
"migration_needed" => "การย้ายฐานข้อมูลไปยัง {0} จะเริ่มต้นหลังจากเข้าสู่ระบบ", "migration_needed" => "การย้ายฐานข้อมูลไปยัง {0} จะเริ่มต้นหลังจากเข้าสู่ระบบ",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "รหัสผ่าน", "password" => "รหัสผ่าน",
"required_username" => "จำเป็นต้องระบุชื่อผู้ใช้งาน", "required_username" => "จำเป็นต้องระบุชื่อผู้ใช้งาน",
"username" => "ชื่อผู้ใช้", "username" => "ชื่อผู้ใช้",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Login", "login" => "Login",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Password", "password" => "Password",
"required_username" => "", "required_username" => "",
"username" => "Username", "username" => "Username",

View File

@@ -9,8 +9,17 @@ return [
"login" => "Giriş", "login" => "Giriş",
"logout" => "Çıkış", "logout" => "Çıkış",
"migration_needed" => "Girişten sonra {0} veri tabanına göç başlayacak.", "migration_needed" => "Girişten sonra {0} veri tabanına göç başlayacak.",
"migration_required" => "Veritabanı Göçü Gerekli",
"migration_auth_message" => "Veritabanı göçünün {0} versiyonuna yetkilendirilmesi için yönetici kimlik bilgileri gereklidir. Devam etmek için giriş yapın.",
"migration_initializing" => "Veritabanı başlatılıyor",
"migration_running" => "Veritabanı göçleri çalışıyor...",
"migration_complete" => "Veritabanı başarıyla başlatıldı!",
"migration_complete_login" => "Artık giriş yapabilirsiniz.",
"migration_failed" => "Göç başarısız oldu",
"migration_error_connection" => "Bağlantı hatası. Lütfen tekrar deneyin.",
"migration_complete_redirect" => "Göç tamamlandı. Girişe yönlendiriliyor...",
"password" => "Parola", "password" => "Parola",
"required_username" => "", "required_username" => "Kullanıcı adı alanı gereklidir.",
"username" => "Kullanıcı Adı", "username" => "Kullanıcı Adı",
"welcome" => "{0}'e Hoş Geldiniz!", "welcome" => "{0}'e Hoş Geldiniz!",
]; ];

View File

@@ -9,6 +9,15 @@ return [
"login" => "Логін", "login" => "Логін",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Пароль", "password" => "Пароль",
"required_username" => "", "required_username" => "",
"username" => "Ім'я користувача", "username" => "Ім'я користувача",

View File

@@ -9,6 +9,15 @@ return [
"login" => "", "login" => "",
"logout" => "", "logout" => "",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "", "password" => "",
"required_username" => "", "required_username" => "",
"username" => "", "username" => "",

View File

@@ -9,6 +9,15 @@ return [
"login" => "Đăng nhập", "login" => "Đăng nhập",
"logout" => "Đăng xuất", "logout" => "Đăng xuất",
"migration_needed" => "Di chuyển cơ sở dữ liệu sang {0} sẽ được bắt đầu sau khi đăng nhập.", "migration_needed" => "Di chuyển cơ sở dữ liệu sang {0} sẽ được bắt đầu sau khi đăng nhập.",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "Mật khẩu", "password" => "Mật khẩu",
"required_username" => "", "required_username" => "",
"username" => "Tài khoản", "username" => "Tài khoản",

View File

@@ -9,6 +9,15 @@ return [
"login" => "登入", "login" => "登入",
"logout" => "退出", "logout" => "退出",
"migration_needed" => "", "migration_needed" => "",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "密码", "password" => "密码",
"username" => "用户名", "username" => "用户名",
"welcome" => "欢迎来到 {0}!", "welcome" => "欢迎来到 {0}!",

View File

@@ -9,6 +9,15 @@ return [
"login" => "登入", "login" => "登入",
"logout" => "登出", "logout" => "登出",
"migration_needed" => "登錄後將開始向 {0} 的數據庫遷移。", "migration_needed" => "登錄後將開始向 {0} 的數據庫遷移。",
"migration_required" => "",
"migration_auth_message" => "",
"migration_initializing" => "",
"migration_running" => "",
"migration_complete" => "",
"migration_complete_login" => "",
"migration_failed" => "",
"migration_error_connection" => "",
"migration_complete_redirect" => "",
"password" => "密碼", "password" => "密碼",
"required_username" => "", "required_username" => "",
"username" => "帳號", "username" => "帳號",

View File

@@ -2,6 +2,7 @@
namespace App\Libraries; namespace App\Libraries;
use CodeIgniter\Database\Exceptions\DatabaseException;
use CodeIgniter\Database\MigrationRunner; use CodeIgniter\Database\MigrationRunner;
use Config\Database; use Config\Database;
use stdClass; use stdClass;
@@ -35,11 +36,16 @@ class MY_Migration extends MigrationRunner
*/ */
public static function get_current_version(): int public static function get_current_version(): int
{ {
$db = Database::connect(); try {
if ($db->tableExists('migrations')) { $db = Database::connect();
$builder = $db->table('migrations'); if ($db->tableExists('migrations')) {
$builder->select('version')->orderBy('version', 'DESC')->limit(1); $builder = $db->table('migrations');
return $builder->get()->getRow()->version; $builder->select('version')->orderBy('version', 'DESC')->limit(1);
$result = $builder->get()->getRow();
return $result ? $result->version : 0;
}
} catch (DatabaseException $e) {
return 0;
} }
return 0; return 0;
@@ -63,10 +69,15 @@ class MY_Migration extends MigrationRunner
*/ */
private function ci3_migrations_exists(): bool|string private function ci3_migrations_exists(): bool|string
{ {
if ($this->db->tableExists('migrations') && !$this->db->fieldExists('id', 'migrations')) { try {
$builder = $this->db->table('migrations'); if ($this->db->tableExists('migrations') && !$this->db->fieldExists('id', 'migrations')) {
$builder->select('version'); $builder = $this->db->table('migrations');
return $builder->get()->getRow()->version; $builder->select('version');
$result = $builder->get()->getRow();
return $result ? $result->version : false;
}
} catch (DatabaseException $e) {
// Database doesn't exist yet or connection failed
} }
return false; return false;
@@ -143,4 +154,5 @@ class MY_Migration extends MigrationRunner
$this->ensureTable(); $this->ensureTable();
} }
} }

View File

@@ -2,6 +2,7 @@
/** /**
* @var bool $has_errors * @var bool $has_errors
* @var bool $is_latest * @var bool $is_latest
* @var bool $is_new_install
* @var string $latest_version * @var string $latest_version
* @var bool $gcaptcha_enabled * @var bool $gcaptcha_enabled
* @var array $config * @var array $config
@@ -46,15 +47,20 @@
<?php endif; ?> <?php endif; ?>
</div> </div>
<section class="box-login d-flex flex-column justify-content-center align-items-center p-md-4"> <section class="box-login d-flex flex-column justify-content-center align-items-center p-md-4">
<?= form_open('login') ?> <?= form_open('login', ['id' => 'login-form']) ?>
<?php if (!$is_latest): ?>
<h3 class="text-center m-0"><?= lang('Login.migration_required') ?></h3> <h3 id="form-heading" class="text-center m-0">
<div class="alert alert-warning mt-3"> <?php if (!$is_latest || $is_new_install): ?>
<strong><?= lang('Login.migration_auth_message', [$latest_version]) ?></strong> <?= lang('Login.migration_required') ?>
</div> <?php else: ?>
<?php else: ?> <?= lang('Login.welcome', [lang('Common.software_short')]) ?>
<h3 class="text-center m-0"><?= lang('Login.welcome', [lang('Common.software_short')]) ?></h3> <?php endif; ?>
<?php endif; ?> </h3>
<div id="migration-warning" class="alert alert-warning mt-3<?= $is_new_install ? '' : ' d-none' ?>">
<strong><?= lang('Login.migration_auth_message', [$latest_version]) ?></strong>
</div>
<?php if ($has_errors): ?> <?php if ($has_errors): ?>
<?php foreach ($validation->getErrors() as $error): ?> <?php foreach ($validation->getErrors() as $error): ?>
<div class="alert alert-danger mt-3"> <div class="alert alert-danger mt-3">
@@ -62,44 +68,72 @@
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php endif; ?> <?php endif; ?>
<?php if (empty($config['login_form']) || 'floating_labels' == ($config['login_form'])): ?>
<div class="form-floating mt-3"> <div id="migration-success" class="alert alert-success d-none mt-3">
<input class="form-control" id="input-username" name="username" type="text" placeholder="<?= lang('Login.username') ?>" <?php if (ENVIRONMENT == "testing") echo 'value="admin"'; ?>> <strong><?= lang('Login.migration_complete') ?></strong> <?= lang('Login.migration_complete_login') ?>
<label for="input-username"><?= lang('Login.username') ?></label> </div>
<div id="migration-progress" class="d-none mt-4">
<h3 class="text-center mb-4"><?= lang('Login.migration_initializing') ?></h3>
<div class="progress mb-3" style="height: 30px;">
<div class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
role="progressbar"
style="width: 100%">
</div>
</div> </div>
<div class="form-floating mb-3"> <p class="text-center text-muted" id="migration-status">
<input class="form-control" id="input-password" name="password" type="password" placeholder="<?= lang('Login.password') ?>" <?php if (ENVIRONMENT == "testing") echo 'value="pointofsale"'; ?>> <?= lang('Login.migration_running') ?>
<label for="input-password"><?= lang('Login.password') ?></label> </p>
</div> </div>
<?php elseif ('input_groups' == ($config['login_form'])): ?>
<div class="input-group mt-3"> <div id="migration-error" class="alert alert-danger d-none mt-3" role="alert">
<span class="input-group-text" id="input-username"> <strong>Error:</strong> <span id="migration-error-message"></span>
<svg class="bi bi-person-fill" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> </div>
<title><?= lang('Common.icon') . '&nbsp;' . lang('Login.username') ?></title>
<path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6" /> <div id="login-fields" class="w-100<?= $is_new_install ? ' d-none' : '' ?>">
</svg> <?php if (empty($config['login_form']) || 'floating_labels' == ($config['login_form'])): ?>
</span> <div class="form-floating mt-3">
<input class="form-control" name="username" type="text" placeholder="<?= lang('Login.username'); ?>" aria-label="<?= lang('Login.username') ?>" aria-describedby="input-username" <?php if (ENVIRONMENT == "testing") echo 'value="admin"'; ?>> <input class="form-control" id="input-username" name="username" type="text" placeholder="<?= lang('Login.username') ?>" <?php if (ENVIRONMENT == "testing") echo 'value="admin"'; ?>>
</div> <label for="input-username"><?= lang('Login.username') ?></label>
<div class="input-group mb-3"> </div>
<span class="input-group-text" id="input-password"> <div class="form-floating mb-3">
<svg class="bi bi-key-fill" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> <input class="form-control" id="input-password" name="password" type="password" placeholder="<?= lang('Login.password') ?>" <?php if (ENVIRONMENT == "testing") echo 'value="pointofsale"'; ?>>
<title><?= lang('Common.icon') . '&nbsp;' . lang('Login.password') ?></title> <label for="input-password"><?= lang('Login.password') ?></label>
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2M2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2" /> </div>
</svg> <?php elseif ('input_groups' == ($config['login_form'])): ?>
</span> <div class="input-group mt-3">
<input class="form-control" name="password" type="password" placeholder="<?= lang('Login.password') ?>" aria-label="<?= lang('Login.password') ?>" aria-describedby="input-password" <?php if (ENVIRONMENT == "testing") echo 'value="pointofsale"'; ?>> <span class="input-group-text" id="input-username">
</div> <svg class="bi bi-person-fill" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<?php endif; ?> <title><?= lang('Common.icon') . '&nbsp;' . lang('Login.username') ?></title>
<?php <path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6" />
if ($gcaptcha_enabled) { </svg>
echo '<script src="https://www.google.com/recaptcha/api.js"></script>'; </span>
echo '<div class="g-recaptcha mb-3" style="text-align: center;" data-sitekey="' . esc($config['gcaptcha_site_key']) . '"></div>'; <input class="form-control" name="username" type="text" placeholder="<?= lang('Login.username'); ?>" aria-label="<?= lang('Login.username') ?>" aria-describedby="input-username" <?php if (ENVIRONMENT == "testing") echo 'value="admin"'; ?>>
} </div>
?> <div class="input-group mb-3">
<span class="input-group-text" id="input-password">
<svg class="bi bi-key-fill" fill="currentColor" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
<title><?= lang('Common.icon') . '&nbsp;' . lang('Login.password') ?></title>
<path d="M3.5 11.5a3.5 3.5 0 1 1 3.163-5H14L15.5 8 14 9.5l-1-1-1 1-1-1-1 1-1-1-1 1H6.663a3.5 3.5 0 0 1-3.163 2M2.5 9a1 1 0 1 0 0-2 1 1 0 0 0 0 2" />
</svg>
</span>
<input class="form-control" name="password" type="password" placeholder="<?= lang('Login.password') ?>" aria-label="<?= lang('Login.password') ?>" aria-describedby="input-password" <?php if (ENVIRONMENT == "testing") echo 'value="pointofsale"'; ?>>
</div>
<?php endif; ?>
<?php if ($gcaptcha_enabled): ?>
<script src="https://www.google.com/recaptcha/api.js"></script>
<div class="g-recaptcha mb-3" style="text-align: center;" data-sitekey="<?= esc($config['gcaptcha_site_key']) ?>"></div>
<?php endif; ?>
</div>
<div class="d-grid"> <div class="d-grid">
<button class="btn btn-lg btn-primary" name="login-button" type="submit"> <button id="submit-button" class="btn btn-lg btn-primary" name="login-button" type="submit">
<?= $is_latest ? lang('Login.go') : lang('Module.migrate') ?> <?php if ($is_new_install): ?>
<?= lang('Module.migrate') ?>
<?php else: ?>
<?= lang('Login.go') ?>
<?php endif; ?>
</button> </button>
</div> </div>
<?= form_close() ?> <?= form_close() ?>
@@ -119,6 +153,141 @@
<span><?= lang('Common.software_title') ?></span> <span><?= lang('Common.software_title') ?></span>
</div> </div>
</footer> </footer>
<?php
use Config\Services;
$request = Services::request();
?>
<?php if (ENVIRONMENT == 'development' || get_cookie('debug') == 'true' || $request->getGet('debug') == 'true') : ?>
<!-- inject:login:debug:js -->
<!-- endinject -->
<?php else : ?>
<!-- inject:login:prod:js -->
<!-- endinject -->
<?php endif; ?>
<script>
const APP_STATE = {
isNewInstall: <?= $is_new_install ? 'true' : 'false' ?>,
isLatest: <?= $is_latest ? 'true' : 'false' ?>,
csrfToken: '<?= csrf_token() ?>',
csrfHash: '<?= csrf_hash() ?>',
migrateUrl: '<?= site_url('migrate') ?>',
loginUrl: '<?= site_url('login') ?>',
i18n: {
welcome: <?= json_encode(lang('Login.welcome', [lang('Common.software_short')])) ?>,
migrate: <?= json_encode(lang('Module.migrate')) ?>,
go: <?= json_encode(lang('Login.go')) ?>,
migrationRequired: <?= json_encode(lang('Login.migration_required')) ?>,
migrationInitializing: <?= json_encode(lang('Login.migration_initializing')) ?>,
migrationRunning: <?= json_encode(lang('Login.migration_running')) ?>,
migrationComplete: <?= json_encode(lang('Login.migration_complete')) ?>,
migrationCompleteLogin: <?= json_encode(lang('Login.migration_complete_login')) ?>,
migrationFailed: <?= json_encode(lang('Login.migration_failed')) ?>,
migrationErrorConnection: <?= json_encode(lang('Login.migration_error_connection')) ?>
}
};
$(document).ready(function() {
const $form = $('#login-form');
const $heading = $('#form-heading');
const $warning = $('#migration-warning');
const $success = $('#migration-success');
const $progress = $('#migration-progress');
const $error = $('#migration-error');
const $errorMessage = $('#migration-error-message');
const $loginFields = $('#login-fields');
const $submitButton = $('#submit-button');
function showMigrationRequired() {
$heading.text(APP_STATE.i18n.migrationRequired);
$warning.removeClass('d-none');
$success.addClass('d-none');
$progress.addClass('d-none');
$error.addClass('d-none');
$loginFields.addClass('d-none');
$submitButton.text(APP_STATE.i18n.migrate);
}
function showMigrationProgress() {
$warning.addClass('d-none');
$success.addClass('d-none');
$error.addClass('d-none');
$loginFields.addClass('d-none');
$progress.removeClass('d-none');
$submitButton.prop('disabled', true);
}
function showMigrationSuccess() {
$progress.addClass('d-none');
$error.addClass('d-none');
$warning.addClass('d-none');
$success.removeClass('d-none');
$heading.text(APP_STATE.i18n.welcome);
$loginFields.removeClass('d-none');
$submitButton.text(APP_STATE.i18n.go);
$submitButton.prop('disabled', false);
}
function showMigrationError(message) {
$progress.addClass('d-none');
$success.addClass('d-none');
$loginFields.addClass('d-none');
$errorMessage.text(message);
$error.removeClass('d-none');
$warning.addClass('d-none');
$submitButton.text(APP_STATE.i18n.migrate);
$submitButton.prop('disabled', false);
}
function showLoginForm() {
$heading.text(APP_STATE.i18n.welcome);
$warning.addClass('d-none');
$progress.addClass('d-none');
$error.addClass('d-none');
$success.addClass('d-none');
$loginFields.removeClass('d-none');
$submitButton.text(APP_STATE.i18n.go);
}
if (!APP_STATE.isNewInstall) {
showLoginForm();
}
$form.on('submit', function(e) {
if (APP_STATE.isNewInstall) {
e.preventDefault();
showMigrationProgress();
$.ajax({
url: APP_STATE.migrateUrl,
type: 'POST',
dataType: 'json',
timeout: 3600000,
data: {
[APP_STATE.csrfToken]: APP_STATE.csrfHash
},
success: function(response) {
if (response.success) {
APP_STATE.isNewInstall = false;
showMigrationSuccess();
} else {
showMigrationError(response.message || APP_STATE.i18n.migrationFailed);
}
},
error: function(xhr, status, error) {
let message = APP_STATE.i18n.migrationErrorConnection;
if (xhr.responseJSON && xhr.responseJSON.message) {
message = xhr.responseJSON.message;
}
showMigrationError(message);
}
});
}
});
});
</script>
</body> </body>
</html> </html>

View File

@@ -190,6 +190,22 @@ gulp.task('prod-js', function() {
}); });
// Inject jQuery into login.php (debug mode) - reuse the jQuery file already created by debug-js
gulp.task('debug-login-js', function() {
// Match only core jQuery (jquery-HASH.js), exclude jquery plugins (jquery-HASH.form.js, etc) and jquery-ui (jquery-ui-HASH.js)
// Pattern: jquery-[hash].js where hash is alphanumeric - core jQuery only
var loginDebugJs = gulp.src(['./public/resources/js/jquery-*.js', '!./public/resources/js/jquery-*.form.js', '!./public/resources/js/jquery-*.validate.js', '!./public/resources/js/jquery-ui-*.js']);
return gulp.src('./app/Views/login.php').pipe(inject(loginDebugJs, {addRootSlash: false, ignorePath: '/public/', starttag: '<!-- inject:login:debug:js -->'})).pipe(gulp.dest('./app/Views'));
});
// Inject jQuery into login.php (production mode) - reuse the jQuery file already created by prod-js
gulp.task('prod-login-js', function() {
// jQuery prod file is already in resources/jquery-*.min.js from prod-js task
var loginProdJs = gulp.src('./public/resources/jquery-*.min.js');
return gulp.src('./app/Views/login.php').pipe(inject(loginProdJs, {addRootSlash: false, ignorePath: '/public/', starttag: '<!-- inject:login:prod:js -->'})).pipe(gulp.dest('./app/Views'));
});
gulp.task('debug-css', function() { gulp.task('debug-css', function() {
var debugcss = gulp.src(['./node_modules/jquery-ui-dist/jquery-ui.css', var debugcss = gulp.src(['./node_modules/jquery-ui-dist/jquery-ui.css',
@@ -289,6 +305,8 @@ gulp.task('default',
'copy-bootstrap', 'copy-bootstrap',
'debug-js', 'debug-js',
'prod-js', 'prod-js',
'debug-login-js',
'prod-login-js',
'debug-css', 'debug-css',
'prod-css', 'prod-css',
'copy-fonts', 'copy-fonts',

View File