Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions language/en/qi.php
Original file line number Diff line number Diff line change
Expand Up @@ -311,4 +311,14 @@
'YES' => 'Yes',

'COLON' => ':',

// Database connection test
'DB_TEST_TYPE_REQUIRED' => 'Database type is required',
'DB_TEST_HOST_REQUIRED' => 'Database host is required',
'DB_TEST_CONNECTION_SUCCESS' => 'Database connection successful',
'DB_TEST_CONNECTION_FAILED' => 'Connection failed',
'DB_TEST_SQLITE3_AVAILABLE' => 'SQLite3 extension is available',
'DB_TEST_SQLITE_AVAILABLE' => 'SQLite extension is available',
'DB_TEST_SQLITE_NOT_AVAILABLE' => 'SQLite extension not available',
'TEST_DATABASE_CONNECTION' => 'Test Database Connection',
));
83 changes: 82 additions & 1 deletion modules/qi_settings.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ public function run()
$saved = false;
$config_text = '';
$errors = [];
if ($mode === 'update_settings')

if ($mode === 'test_db_connection')
{
$this->test_db_connection();
return;
}
else if ($mode === 'update_settings')
{
$qi_config = @utf8_normalize_nfc(qi_request_var('qi_config', array('' => ''), true));

Expand Down Expand Up @@ -147,4 +153,79 @@ public function run()

qi::page_display('settings_body');
}

private function test_db_connection()
{
header('Content-Type: application/json');

$dbms = qi_request_var('dbms', '');
$dbhost = qi_request_var('dbhost', '');
$dbport = qi_request_var('dbport', '');
$dbuser = qi_request_var('dbuser', '');
$dbpasswd = qi_request_var('dbpasswd', '');

if (empty($dbms))
{
echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_TYPE_REQUIRED')]);
return;
}

// SQLite doesn't use server connections, just test if extension is available
if (in_array($dbms, ['sqlite', 'sqlite3']))
{
try
{
if ($dbms === 'sqlite3')
{
new \SQLite3(':memory:');
echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_SQLITE3_AVAILABLE')]);
}
else
{
$error = null;
@sqlite_open(':memory:', 0666, $error);
echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_SQLITE_AVAILABLE')]);
}
}
catch (Exception $e)
{
echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_SQLITE_NOT_AVAILABLE')]);
}
return;
}

if (empty($dbhost))
{
echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_HOST_REQUIRED')]);
return;
}

// we need to capture trigger_error() calls to be able to continue
set_error_handler(function() {
return true;
});

try
{
$db_data = [$dbms, $dbhost, $dbuser, $dbpasswd, $dbport, ''];
$db = db_connect($db_data);

if ($db && $db->db_connect_id)
{
$db->sql_close();
restore_error_handler();
echo json_encode(['success' => true, 'message' => qi::lang('DB_TEST_CONNECTION_SUCCESS')]);
}
else
{
restore_error_handler();
echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_CONNECTION_FAILED')]);
}
}
catch (Exception $e)
{
restore_error_handler();
echo json_encode(['success' => false, 'message' => qi::lang('DB_TEST_CONNECTION_FAILED')]);
}
}
}
42 changes: 42 additions & 0 deletions style/assets/js/scripts.js
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,48 @@
});
}

// Database connection test
const $testDbBtn = $('#test-db-connection');
if ($testDbBtn) {
$testDbBtn.addEventListener('click', () => {
const $result = $('#db-test-result');
$testDbBtn.disabled = true;
$testDbBtn.innerHTML = '<span class="spinner-border spinner-border-sm" role="status"></span> Testing...';

const formData = new FormData();
formData.append('dbms', $('#dbms').value);
formData.append('dbhost', $('#dbhost').value);
formData.append('dbport', $('#dbport').value);
formData.append('dbuser', $('#dbuser').value);
formData.append('dbpasswd', $('#dbpasswd').value);

const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.addEventListener('loadend', () => {
$testDbBtn.disabled = false;
$testDbBtn.innerHTML = '<svg class="bi" width="16" height="16" fill="currentColor"><use xlink:href="style/assets/img/bootstrap-icons.svg#database-check"/></svg> Test Database Connection';

if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const response = xhr.response;
if (response.success) {
$result.className = 'mt-2 alert alert-success';
$result.innerHTML = '<svg class="bi text-success" width="16" height="16" fill="currentColor"><use xlink:href="style/assets/img/bootstrap-icons.svg#check-circle-fill"/></svg> ' + response.message;
} else {
$result.className = 'mt-2 alert alert-danger';
$result.innerHTML = '<svg class="bi text-danger" width="16" height="16" fill="currentColor"><use xlink:href="style/assets/img/bootstrap-icons.svg#exclamation-triangle-fill"/></svg> ' + response.message;
}
} else {
$result.className = 'mt-2 alert alert-danger';
$result.innerHTML = '<svg class="bi text-danger" width="16" height="16" fill="currentColor"><use xlink:href="style/assets/img/bootstrap-icons.svg#exclamation-triangle-fill"/></svg> Connection test failed';
}
});

xhr.open('POST', 'index.php?page=settings&mode=test_db_connection');
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.send(formData);
});
}

// Notification of QI update (use sessionStorage for dismissed notification)
if (sessionStorage.getItem('qiupdate') === null) {
const qiUpdateToast = $('#qiUpdateToast');
Expand Down
9 changes: 9 additions & 0 deletions style/settings_body.twig
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,15 @@
<label for="table_prefix" class="col-md-5 form-label">{{ lang('TABLE_PREFIX') ~ lang('COLON') }}<br><span class="form-text text-muted">{{ lang('TABLE_PREFIX_EXPLAIN') }} {{ lang('THIS_CAN_CHANGE') }}</span></label>
<div class="col-md-7"><input class="form-control" type="text" id="table_prefix" name="qi_config[table_prefix]" value="{{ CONFIG_TABLE_PREFIX }}" placeholder="{{ lang('REQUIRED') }}"></div>
</div>
<div class="mb-3 row">
<div class="col-md-7 offset-md-5">
<button type="button" id="test-db-connection" class="btn btn-outline-primary">
<svg class="bi" width="16" height="16" fill="currentColor"><use xlink:href="{{ QI_ROOT_PATH }}style/assets/img/bootstrap-icons.svg#database-check"/></svg>
{{ lang('TEST_DATABASE_CONNECTION') }}
</button>
<div id="db-test-result" class="mt-2 d-none"></div>
</div>
</div>
</fieldset>
</div>
</div>
Expand Down