Import alt-mysql plugin
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
final class AppContext
|
||||
{
|
||||
public DirectAdminUser $daUser;
|
||||
public Settings $settings;
|
||||
public MySQLService $mysql;
|
||||
|
||||
private CsrfGuard $csrf;
|
||||
private Lang $lang;
|
||||
|
||||
public function __construct(DirectAdminUser $daUser, Settings $settings, MySQLService $mysql, CsrfGuard $csrf, Lang $lang)
|
||||
{
|
||||
$this->daUser = $daUser;
|
||||
$this->settings = $settings;
|
||||
$this->mysql = $mysql;
|
||||
$this->csrf = $csrf;
|
||||
$this->lang = $lang;
|
||||
}
|
||||
|
||||
public function action(): string
|
||||
{
|
||||
return (string)PLUGIN_ACTION;
|
||||
}
|
||||
|
||||
public function baseUrl(): string
|
||||
{
|
||||
return '/CMD_PLUGINS/alt-mysql';
|
||||
}
|
||||
|
||||
public function url(string $page, array $query = []): string
|
||||
{
|
||||
$path = $this->baseUrl() . '/' . ltrim($page, '/');
|
||||
if (!empty($query)) {
|
||||
$path .= '?' . http_build_query($query);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function isPost(): bool
|
||||
{
|
||||
return Http::isPost();
|
||||
}
|
||||
|
||||
public function postString(string $key, string $default = ''): string
|
||||
{
|
||||
return Http::getString($_POST, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,string>
|
||||
*/
|
||||
public function postArray(string $key): array
|
||||
{
|
||||
$raw = Http::getArray($_POST, $key);
|
||||
$out = [];
|
||||
foreach ($raw as $item) {
|
||||
if (is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = trim((string)$item);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function queryString(string $key, string $default = ''): string
|
||||
{
|
||||
return Http::getString($_GET, $key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string,string|int|float> $vars
|
||||
*/
|
||||
public function t(string $key, array $vars = []): string
|
||||
{
|
||||
return $this->lang->t($key, $vars);
|
||||
}
|
||||
|
||||
public function formatException(Throwable $e): string
|
||||
{
|
||||
if ($e instanceof LocalizedException) {
|
||||
return $this->t($e->key(), $e->vars());
|
||||
}
|
||||
return $this->t($e->getMessage());
|
||||
}
|
||||
|
||||
public function csrfField(string $intent): string
|
||||
{
|
||||
$issued = $this->csrf->issue($intent);
|
||||
$ts = (string)$issued['ts'];
|
||||
$sid = htmlspecialchars((string)($issued['sid'] ?? ''), ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
$token = htmlspecialchars($issued['token'], ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
|
||||
return '<input type="hidden" name="csrf_ts" value="' . $ts . '">' .
|
||||
'<input type="hidden" name="csrf_sid" value="' . $sid . '">' .
|
||||
'<input type="hidden" name="csrf_token" value="' . $token . '">';
|
||||
}
|
||||
|
||||
public function requireCsrf(string $intent): void
|
||||
{
|
||||
$ts = $this->postString('csrf_ts');
|
||||
$sid = $this->postString('csrf_sid');
|
||||
$token = $this->postString('csrf_token');
|
||||
if (!$this->csrf->validate($intent, $ts, $token, 3600, $sid)) {
|
||||
$debug = sprintf(
|
||||
'[%s] CSRF_FAIL intent=%s action=%s method=%s ts=%s sid=%s token_prefix=%s post_keys=%s',
|
||||
date('c'),
|
||||
$intent,
|
||||
$this->action(),
|
||||
Http::method(),
|
||||
$ts,
|
||||
$sid,
|
||||
substr($token, 0, 12),
|
||||
implode(',', array_keys($_POST))
|
||||
);
|
||||
@error_log($debug . "\n", 3, PLUGIN_ROOT . '/error.log');
|
||||
throw new RuntimeException('Nieprawidłowy lub wygasły token CSRF.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $okMessages
|
||||
* @param string[] $errorMessages
|
||||
*/
|
||||
public function renderPage(string $title, string $body, array $okMessages = [], array $errorMessages = [], string $logs = ''): void
|
||||
{
|
||||
$alerts = '';
|
||||
foreach ($okMessages as $msg) {
|
||||
$alerts .= '<div class="alert alert-ok">' . self::e($msg) . '</div>';
|
||||
}
|
||||
foreach ($errorMessages as $msg) {
|
||||
$alerts .= '<div class="alert alert-err">' . self::e($msg) . '</div>';
|
||||
}
|
||||
|
||||
$alerts = $this->lang->translateHtml($alerts);
|
||||
$body = $this->lang->translateHtml($body);
|
||||
$logs = $this->lang->translateHtml($logs);
|
||||
|
||||
$safeTitle = self::e($this->t($title));
|
||||
$topActions = $this->renderTopActions();
|
||||
$langCode = $this->daUser->language();
|
||||
|
||||
header('Content-Type: text/html; charset=UTF-8');
|
||||
echo '<!doctype html><html lang="' . self::e($langCode) . '"><head><meta charset="utf-8">';
|
||||
echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
|
||||
echo '<title>' . $safeTitle . '</title>';
|
||||
echo '<style>' . $this->styles() . '</style>';
|
||||
echo '</head><body>';
|
||||
echo '<div class="wrap">';
|
||||
echo '<div class="breadcrumbs">' . self::e($this->t('Pulpit')) . ' <span>></span> ' . self::e($this->t('Bazy danych')) . '</div>';
|
||||
$headerClass = in_array($this->action(), ['index', 'upload_backup'], true) ? ' class="header-center"' : '';
|
||||
echo '<header' . $headerClass . '><h1>' . $safeTitle . '</h1></header>';
|
||||
echo '<div class="top-actions">' . $topActions . '</div>';
|
||||
echo '<section class="main-section">' . $alerts . $body . '</section>';
|
||||
echo '<section class="logs-section">' . $logs . '</section>';
|
||||
echo '</div>';
|
||||
$jsConfig = 'window.DA_MYSQL_BASE_URL=' . json_encode($this->baseUrl(), JSON_UNESCAPED_SLASHES) . ';';
|
||||
$jsConfig .= 'window.DA_MYSQL_INDEX_URL=' . json_encode($this->url('index.html'), JSON_UNESCAPED_SLASHES) . ';';
|
||||
$jsConfig .= 'window.DA_MYSQL_USER_URL=' . json_encode($this->url('user/index.html'), JSON_UNESCAPED_SLASHES) . ';';
|
||||
$jsConfig .= 'window.DA_MYSQL_UPLOAD_RAW_URL=' . json_encode($this->url('upload_backup.raw'), JSON_UNESCAPED_SLASHES) . ';';
|
||||
$jsConfig .= 'window.DA_MYSQL_I18N=' . json_encode($this->jsI18n(), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE) . ';';
|
||||
echo '<script>' . $jsConfig . '</script>';
|
||||
echo '<script>' . $this->scripts() . '</script>';
|
||||
echo '</body></html>';
|
||||
}
|
||||
|
||||
public function renderFatal(string $message, int $code = 400): void
|
||||
{
|
||||
http_response_code($code);
|
||||
$body = '<p>' . self::e($this->t($message)) . '</p>';
|
||||
$body .= '<p><a href="' . self::e($this->url('index.html')) . '">' . self::e($this->t('Powrót')) . '</a></p>';
|
||||
$this->renderPage('Błąd', $body, [], []);
|
||||
}
|
||||
|
||||
public static function e(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||||
}
|
||||
|
||||
private function renderTopActions(): string
|
||||
{
|
||||
$links = [];
|
||||
|
||||
$links[] = '<a class="btn amber' . ($this->action() === 'index' ? ' active' : '') . '" href="' . self::e($this->url('index.html')) . '">' . self::e($this->t('BAZY DANYCH')) . '</a>';
|
||||
$links[] = '<a class="btn amber' . ($this->action() === 'change_password' ? ' active' : '') . '" href="' . self::e($this->url('change_password.html')) . '">' . self::e($this->t('UŻYTKOWNICY BAZ DANYCH')) . '</a>';
|
||||
if ($this->settings->enableUploadBackup()) {
|
||||
$links[] = '<a class="btn amber' . ($this->action() === 'upload_backup' ? ' active' : '') . '" href="' . self::e($this->url('upload_backup.html')) . '">' . self::e($this->t('BACKUPY BAZ DANYCH')) . '</a>';
|
||||
}
|
||||
if ($this->settings->enableAdminer()) {
|
||||
$links[] = '<form method="post" target="_top" action="' . self::e($this->url('open_phpmyadmin.raw')) . '" style="display:inline-flex;align-items:center;">' .
|
||||
$this->csrfField('open_phpmyadmin_submit') .
|
||||
'<button class="btn amber' . ($this->action() === 'open_phpmyadmin' ? ' active' : '') . '" type="submit">' . self::e($this->t('PHPMYADMIN')) . '</button>' .
|
||||
'</form>';
|
||||
}
|
||||
|
||||
return implode('', $links);
|
||||
}
|
||||
|
||||
private function styles(): string
|
||||
{
|
||||
$skin = $this->daUser->skin();
|
||||
$path = PLUGIN_ROOT . '/user/' . $skin . '.css';
|
||||
if (!is_file($path)) {
|
||||
$path = PLUGIN_ROOT . '/user/enhanced.css';
|
||||
}
|
||||
$css = is_file($path) ? file_get_contents($path) : '';
|
||||
if ($css === false) {
|
||||
return '';
|
||||
}
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string,string>
|
||||
*/
|
||||
private function jsI18n(): array
|
||||
{
|
||||
$keys = [
|
||||
'Pobieranie...',
|
||||
'Nieudana odpowiedź serwera ({code})',
|
||||
'Nieudana odpowiedź serwera (HTML zamiast pliku, URL: {url})',
|
||||
'Serwer zwrócił pusty plik.',
|
||||
'Nie można pobrać pliku backupu.',
|
||||
'Pobierz plik backupu',
|
||||
'Brak plików .sql/.gz',
|
||||
'Brak katalogów',
|
||||
'Nie udało się wczytać katalogów.',
|
||||
'Podaj nazwę katalogu.',
|
||||
'Nie udało się utworzyć katalogu.',
|
||||
'Wysyłanie pliku...',
|
||||
'Nie udało się wysłać pliku backupu.',
|
||||
'Wybierz plik .sql/.gz/.sql.gz lub wskaż ścieżkę pliku na serwerze.',
|
||||
'Wskaż ścieżkę do pliku SQL lub SQL.GZ.',
|
||||
'Wybierz plik .sql/.gz/.sql.gz do przywrócenia.',
|
||||
];
|
||||
|
||||
$out = [];
|
||||
foreach ($keys as $key) {
|
||||
$out[$key] = $this->t($key);
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function scripts(): string
|
||||
{
|
||||
$js = <<<'JS'
|
||||
(function () {
|
||||
var __i18n = (typeof window.DA_MYSQL_I18N === 'object' && window.DA_MYSQL_I18N) ? window.DA_MYSQL_I18N : {};
|
||||
function tr(key) {
|
||||
return (__i18n && Object.prototype.hasOwnProperty.call(__i18n, key)) ? __i18n[key] : key;
|
||||
}
|
||||
function trf(key, vars) {
|
||||
var out = tr(key);
|
||||
if (!vars) {
|
||||
return out;
|
||||
}
|
||||
for (var k in vars) {
|
||||
if (!Object.prototype.hasOwnProperty.call(vars, k)) {
|
||||
continue;
|
||||
}
|
||||
out = out.replace(new RegExp('\\{' + k + '\\}', 'g'), String(vars[k]));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function generatePassword(len) {
|
||||
var size = len || 20;
|
||||
var alphabet = 'ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz23456789!@#$%^&*_-+=';
|
||||
var bytes = new Uint32Array(size);
|
||||
if (window.crypto && window.crypto.getRandomValues) {
|
||||
window.crypto.getRandomValues(bytes);
|
||||
} else {
|
||||
for (var i = 0; i < size; i += 1) {
|
||||
bytes[i] = Math.floor(Math.random() * 0xffffffff);
|
||||
}
|
||||
}
|
||||
var out = '';
|
||||
for (var j = 0; j < size; j += 1) {
|
||||
out += alphabet.charAt(bytes[j] % alphabet.length);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function bindPasswordHelpers() {
|
||||
document.querySelectorAll('[data-generate-target]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var input = document.getElementById(btn.getAttribute('data-generate-target'));
|
||||
if (!input) { return; }
|
||||
input.value = generatePassword(20);
|
||||
input.dispatchEvent(new Event('input', { bubbles: true }));
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-toggle-target]').forEach(function (btn) {
|
||||
btn.addEventListener('click', function () {
|
||||
var input = document.getElementById(btn.getAttribute('data-toggle-target'));
|
||||
if (!input) { return; }
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function bindDownloadForms() {
|
||||
document.querySelectorAll('form[data-backup-download]').forEach(function (form) {
|
||||
form.addEventListener('submit', function (ev) {
|
||||
var btn = form.querySelector('button[type="submit"],input[type="submit"]');
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.dataset.originalText = btn.textContent;
|
||||
btn.textContent = tr('Pobieranie...');
|
||||
}
|
||||
window.setTimeout(function () {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
if (btn.dataset.originalText) {
|
||||
btn.textContent = btn.dataset.originalText;
|
||||
}
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.daMysqlI18n = { tr: tr, trf: trf };
|
||||
bindPasswordHelpers();
|
||||
bindDownloadForms();
|
||||
}());
|
||||
JS;
|
||||
|
||||
return FilePicker::scripts() . "\n" . $js;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user