Merge branch 'QA_5_2'
Already merged in master: https://github.com/phpmyadmin/phpmyadmin/pull/19960 Re-write of the test from4de9f0f0b1Merge of631cac096fMerge of58cee9656fbut could not find the code to implement Signed-off-by: William Desportes <williamdes@wdes.fr>
This commit is contained in:
commit
4da3aaaf7a
8
.github/workflows/daily-snapshots.yml
vendored
8
.github/workflows/daily-snapshots.yml
vendored
@ -25,7 +25,7 @@ jobs:
|
||||
- { version: '6.0', branch: 'master', php-version: '8.2', node-version: '20', python-version: '3.11' }
|
||||
steps:
|
||||
- name: Check out code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ matrix.branch }}
|
||||
|
||||
@ -40,13 +40,13 @@ jobs:
|
||||
coverage: none
|
||||
|
||||
- name: Set up Node ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Set up Python ${{ matrix.python-version }}
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
continue-on-error: true
|
||||
|
||||
- name: Upload snapshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: phpMyAdmin-${{ matrix.version }}-snapshots
|
||||
path: release/*
|
||||
|
||||
12
.github/workflows/frontend-tests.yml
vendored
12
.github/workflows/frontend-tests.yml
vendored
@ -23,10 +23,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
@ -42,10 +42,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
@ -61,10 +61,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
|
||||
6
.github/workflows/lint-and-analyse-php.yml
vendored
6
.github/workflows/lint-and-analyse-php.yml
vendored
@ -19,7 +19,7 @@ jobs:
|
||||
php-version: ["8.2"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up PHP ${{ matrix.php-version }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
@ -40,7 +40,7 @@ jobs:
|
||||
- name: Cache coding-standard
|
||||
# Allow the previous check to fail but not abort
|
||||
if: always()
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: .phpcs-cache
|
||||
key: phpcs-cache
|
||||
@ -67,7 +67,7 @@ jobs:
|
||||
php-version: ["8.2"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Set up PHP ${{ matrix.php-version }}
|
||||
uses: shivammathur/setup-php@v2
|
||||
|
||||
2
.github/workflows/lint-docs.yml
vendored
2
.github/workflows/lint-docs.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Lint phpdoc blocks
|
||||
uses: sudo-bot/action-doctum@v5
|
||||
|
||||
4
.github/workflows/mutation-tests.yml
vendored
4
.github/workflows/mutation-tests.yml
vendored
@ -34,13 +34,13 @@ jobs:
|
||||
- ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Checkout base ref code
|
||||
if: github.event_name == 'pull_request'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
ref: ${{ github.base_ref }}
|
||||
|
||||
|
||||
8
.github/workflows/other-tools.yml
vendored
8
.github/workflows/other-tools.yml
vendored
@ -16,7 +16,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Sphinx for the documentation build
|
||||
run: |
|
||||
@ -33,7 +33,7 @@ jobs:
|
||||
php-version: ["8.2"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install gettext
|
||||
run: sudo apt-get install -y gettext
|
||||
@ -49,13 +49,13 @@ jobs:
|
||||
uses: ramsey/composer-install@v3
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
uses: actions/setup-python@v6
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
|
||||
2
.github/workflows/shellcheck.yml
vendored
2
.github/workflows/shellcheck.yml
vendored
@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Run ShellCheck
|
||||
uses: reviewdog/action-shellcheck@v1
|
||||
|
||||
6
.github/workflows/test-selenium.yml
vendored
6
.github/workflows/test-selenium.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
||||
- "Xss"
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install gettext
|
||||
run: sudo apt-get update && sudo apt-get install -y gettext
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
uses: ramsey/composer-install@v3
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '20'
|
||||
cache: 'yarn'
|
||||
@ -148,7 +148,7 @@ jobs:
|
||||
./tests/stop-local-server
|
||||
|
||||
- name: Upload screenshots
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v6
|
||||
if: ${{ failure() }}
|
||||
with:
|
||||
name: selenium-screenshots
|
||||
|
||||
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@ -28,7 +28,7 @@ jobs:
|
||||
- { php-version: '82', experimental: false, arch: 's390x', exclude-phpunit-groups: '--exclude-group extension-iconv --exclude-group 32bit-incompatible' }
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Write script
|
||||
# tcpdf allowed memory exhausted needs the memory_limit workaround
|
||||
@ -78,7 +78,7 @@ jobs:
|
||||
php-extensions: 'mbstring, iconv, mysqli, zip, bz2'
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Fetch some commits for Scrutinizer coverage upload
|
||||
fetch-depth: 15
|
||||
@ -139,7 +139,7 @@ jobs:
|
||||
extension: ["dbase"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
# Fetch some commits for Scrutinizer coverage upload
|
||||
fetch-depth: 15
|
||||
|
||||
2
.github/workflows/update-po.yml
vendored
2
.github/workflows/update-po.yml
vendored
@ -21,7 +21,7 @@ jobs:
|
||||
php-version: ["8.2"]
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Install Gettext
|
||||
run: |
|
||||
|
||||
@ -204,18 +204,6 @@ parameters:
|
||||
count: 1
|
||||
path: examples/signon.php
|
||||
|
||||
-
|
||||
message: '#^Comparison operation "\<" between int\<80200, 80599\> and 80200 is always false\.$#'
|
||||
identifier: smaller.alwaysFalse
|
||||
count: 1
|
||||
path: public/index.php
|
||||
|
||||
-
|
||||
message: '#^Comparison operation "\<" between int\<80200, 80599\> and 80200 is always false\.$#'
|
||||
identifier: smaller.alwaysFalse
|
||||
count: 1
|
||||
path: public/setup/index.php
|
||||
|
||||
-
|
||||
message: '#^Loose comparison via "\=\=" is not allowed\.$#'
|
||||
identifier: equal.notAllowed
|
||||
@ -17928,12 +17916,24 @@ parameters:
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Cannot access offset ''secret'' on mixed\.$#'
|
||||
identifier: offsetAccess.nonOffsetAccessible
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Cannot access offset ''server_2'' on mixed\.$#'
|
||||
identifier: offsetAccess.nonOffsetAccessible
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Cannot access offset ''settings'' on mixed\.$#'
|
||||
identifier: offsetAccess.nonOffsetAccessible
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Cannot access offset ''ts'' on mixed\.$#'
|
||||
identifier: offsetAccess.nonOffsetAccessible
|
||||
@ -17958,6 +17958,18 @@ parameters:
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#2 \$result of method PhpMyAdmin\\Tests\\Stubs\\DbiDummy\:\:addResult\(\) expects bool\|list\<non\-empty\-list\<float\|int\|string\|null\>\>, array\{array\{non\-empty\-string\|false, 1767029179\}\} given\.$#'
|
||||
identifier: argument.type
|
||||
count: 2
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Parameter \#3 \$subject of function str_replace expects array\<string\>\|string, string\|false given\.$#'
|
||||
identifier: argument.type
|
||||
count: 1
|
||||
path: tests/unit/UserPreferencesTest.php
|
||||
|
||||
-
|
||||
message: '#^Property PhpMyAdmin\\Config\:\:\$settings \(array\{PmaAbsoluteUri\: string, AuthLog\: string, AuthLogSuccess\: bool, PmaNoRelation_DisableWarning\: bool, SuhosinDisableWarning\: bool, LoginCookieValidityDisableWarning\: bool, ReservedWordDisableWarning\: bool, TranslationWarningThreshold\: int, \.\.\.\}\) does not accept array\{''Server/hide_db''\: ''testval123'', ''Server/port''\: ''213''\}\.$#'
|
||||
identifier: assign.propertyType
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
includes:
|
||||
- phpstan-baseline.neon
|
||||
parameters:
|
||||
phpVersion: 80200 # PHP 8.2
|
||||
level: max
|
||||
paths:
|
||||
- app
|
||||
|
||||
@ -1 +1 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="m6 6v18h18v-18zm14 8v2h-10v-2z"/></svg>
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><rect x="7" y="7" width="14" height="14" fill="white"/><path d="m6 6v18h18v-18zm14 8v2h-10v-2z"/></svg>
|
||||
|
Before Width: | Height: | Size: 108 B After Width: | Height: | Size: 164 B |
@ -1 +1 @@
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><path d="m6 6v18h18v-18zm14 8v2h-4v4h-2v-4h-4v-2h4v-4h2v4z"/></svg>
|
||||
<svg viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><rect x="6" y="6" width="14" height="14" fill="white"/><path d="m6 6v18h18v-18zm14 8v2h-4v4h-2v-4h-4v-2h4v-4h2v4z"/></svg>
|
||||
|
Before Width: | Height: | Size: 127 B After Width: | Height: | Size: 182 B |
24
resources/js/modules/json-highlight.ts
Normal file
24
resources/js/modules/json-highlight.ts
Normal file
@ -0,0 +1,24 @@
|
||||
|
||||
/**
|
||||
* Applies JSON syntax highlighting transformation using CodeMirror
|
||||
*
|
||||
* @param {JQuery} $base base element which contains the JSON code blocks
|
||||
*/
|
||||
export default function highlightJson ($base) {
|
||||
var $elm = $base.find('code.json');
|
||||
$elm.each(function () {
|
||||
var $json = $(this);
|
||||
var $pre = $json.find('pre');
|
||||
/* We only care about visible elements to avoid double processing */
|
||||
if ($pre.is(':visible')) {
|
||||
var $highlight = $('<div class="json-highlight cm-s-default"></div>');
|
||||
$json.append($highlight);
|
||||
// @ts-ignore
|
||||
if (typeof window.CodeMirror !== 'undefined' && typeof window.CodeMirror.runMode === 'function') {
|
||||
// @ts-ignore
|
||||
window.CodeMirror.runMode($json.text(), 'application/json', $highlight[0]);
|
||||
$pre.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -6,6 +6,7 @@ import { Navigation } from './modules/navigation.ts';
|
||||
import { CommonParams } from './modules/common.ts';
|
||||
import createProfilingChart from './modules/functions/createProfilingChart.ts';
|
||||
import highlightSql from './modules/sql-highlight.ts';
|
||||
import highlightJson from './modules/json-highlight.ts';
|
||||
import { ajaxRemoveMessage, ajaxShowMessage } from './modules/ajax-message.ts';
|
||||
import { escapeBacktick, escapeHtml } from './modules/functions/escape.ts';
|
||||
import refreshMainContent from './modules/functions/refreshMainContent.ts';
|
||||
@ -943,6 +944,7 @@ AJAX.registerOnload('sql.js', function () {
|
||||
.trigger('makeGrid');
|
||||
|
||||
highlightSql($sqlqueryresults);
|
||||
highlightJson($sqlqueryresults);
|
||||
}); // end $.post()
|
||||
}); // end displayOptionsForm handler
|
||||
|
||||
|
||||
@ -1,21 +1,10 @@
|
||||
import $ from 'jquery';
|
||||
import { AJAX } from '../modules/ajax.ts';
|
||||
import highlightJson from '../modules/json-highlight.ts';
|
||||
|
||||
/**
|
||||
* JSON syntax highlighting transformation plugin
|
||||
*/
|
||||
AJAX.registerOnload('transformations/json.js', function () {
|
||||
var $elm = $('#page_content').find('code.json');
|
||||
$elm.each(function () {
|
||||
var $json = $(this);
|
||||
var $pre = $json.closest('pre');
|
||||
/* We only care about visible elements to avoid double processing */
|
||||
if ($json.is(':visible')) {
|
||||
var $highlight = $('<div class="json-highlight cm-s-default"></div>');
|
||||
$pre.append($highlight);
|
||||
// @ts-ignore
|
||||
window.CodeMirror.runMode($json.text(), 'application/json', $highlight[0]);
|
||||
$json.hide();
|
||||
}
|
||||
});
|
||||
highlightJson($('#page_content'));
|
||||
});
|
||||
|
||||
@ -234,7 +234,11 @@
|
||||
|
||||
<td colspan="{{ action_colspan }}"
|
||||
class="text-center">
|
||||
{% if current_table['Comment'] is not empty %}
|
||||
{{ current_table['Comment'] }}
|
||||
{% else %}
|
||||
{{ t('in use') }}
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
{% if table is not empty %}
|
||||
<li class="breadcrumb-item">
|
||||
{{ show_icons('TabsMode') ? get_image(table.is_view ? 'b_views' : 's_tbl') }}
|
||||
<a href="{{ url(table.url, {'db': database.name, 'table': table.name}) }}" data-raw-text="{{ table.name }}" draggable="false">
|
||||
<a href="{{ url(table.url, {'db': database.name, 'table': table.name, 'pos': 0}) }}" data-raw-text="{{ table.name }}" draggable="false">
|
||||
{% if show_text('TabsMode') %}
|
||||
{% if table.is_view %}
|
||||
{{ t('View:') }}
|
||||
|
||||
@ -51,6 +51,7 @@ use function rtrim;
|
||||
use function sprintf;
|
||||
use function str_contains;
|
||||
use function str_replace;
|
||||
use function str_starts_with;
|
||||
use function stripos;
|
||||
use function strtolower;
|
||||
use function strtoupper;
|
||||
@ -517,6 +518,13 @@ class Table implements Stringable
|
||||
}
|
||||
} elseif ($type === 'BINARY' || $type === 'VARBINARY') {
|
||||
$query .= ' DEFAULT 0x' . $defaultValue;
|
||||
} elseif ($defaultValue === 'unix_timestamp()' || $defaultValue === 'UNIX_TIMESTAMP()') {
|
||||
$query .= ' DEFAULT ' . $defaultValue;
|
||||
} elseif (
|
||||
str_starts_with($defaultValue, 'current_timestamp')
|
||||
|| str_starts_with($defaultValue, 'CURRENT_TIMESTAMP')
|
||||
) {
|
||||
$query .= ' DEFAULT CURRENT_TIMESTAMP(' . $length . ')';
|
||||
} else {
|
||||
$query .= ' DEFAULT ' . $dbi->quoteString($defaultValue);
|
||||
}
|
||||
|
||||
@ -115,6 +115,16 @@ class UserPreferences
|
||||
*/
|
||||
public function save(array $configArray): true|Message
|
||||
{
|
||||
// Smart merge: If this looks like a partial save (missing 2fa but we have it stored),
|
||||
// automatically merge to prevent accidental overwrites
|
||||
if (! isset($configArray['2fa'])) {
|
||||
$existingPrefs = $this->load();
|
||||
if (isset($existingPrefs['config_data']['2fa'])) {
|
||||
// This is likely a partial save from page settings - merge to preserve 2fa
|
||||
$configArray = array_merge($existingPrefs['config_data'], $configArray);
|
||||
}
|
||||
}
|
||||
|
||||
$relationParameters = $this->relation->getRelationParameters();
|
||||
$cacheKey = 'server_' . Current::$server;
|
||||
if (
|
||||
|
||||
@ -923,6 +923,113 @@ class TableTest extends AbstractTestCase
|
||||
);
|
||||
}
|
||||
|
||||
public function testGenerateFieldSpecForRename(): void
|
||||
{
|
||||
// Rename a TIMESTAMP column from created_at to created_ts
|
||||
$name = 'created_ts';
|
||||
$type = 'TIMESTAMP';
|
||||
$length = '6';
|
||||
$attribute = '';
|
||||
$collation = '';
|
||||
$null = 'NO';
|
||||
$defaultType = 'USER_DEFINED';
|
||||
$defaultValue = 'current_timestamp(6)';
|
||||
$extra = '';
|
||||
$comment = '';
|
||||
$virtuality = '';
|
||||
$expression = '';
|
||||
$moveTo = '';
|
||||
$columnsWithIndex = ['id'];
|
||||
$oldColumnName = 'created_at';
|
||||
|
||||
$query = Table::generateFieldSpec(
|
||||
$name,
|
||||
$type,
|
||||
$length,
|
||||
$attribute,
|
||||
$collation,
|
||||
$null,
|
||||
$defaultType,
|
||||
$defaultValue,
|
||||
$extra,
|
||||
$comment,
|
||||
$virtuality,
|
||||
$expression,
|
||||
$moveTo,
|
||||
);
|
||||
self::assertSame('`created_ts` TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)', $query);
|
||||
|
||||
// Rename an INT column from created_at to created_ts
|
||||
// This default values does not insert, but the column definition is valid
|
||||
// And can be renamed
|
||||
$name = 'created_ts';
|
||||
$type = 'INT';
|
||||
$length = '6';
|
||||
$attribute = '';
|
||||
$collation = '';
|
||||
$null = 'NO';
|
||||
$defaultType = 'USER_DEFINED';
|
||||
$defaultValue = 'current_timestamp(6)';
|
||||
$extra = '';
|
||||
$comment = '';
|
||||
$virtuality = '';
|
||||
$expression = '';
|
||||
$moveTo = '';
|
||||
$columnsWithIndex = ['id'];
|
||||
$oldColumnName = 'created_at';
|
||||
|
||||
$query = Table::generateFieldSpec(
|
||||
$name,
|
||||
$type,
|
||||
$length,
|
||||
$attribute,
|
||||
$collation,
|
||||
$null,
|
||||
$defaultType,
|
||||
$defaultValue,
|
||||
$extra,
|
||||
$comment,
|
||||
$virtuality,
|
||||
$expression,
|
||||
$moveTo,
|
||||
);
|
||||
self::assertSame('`created_ts` INT(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)', $query);
|
||||
|
||||
// Rename an INT column from created_at to created_ts
|
||||
$name = 'created_ts';
|
||||
$type = 'INT';
|
||||
$length = '6';
|
||||
$attribute = '';
|
||||
$collation = '';
|
||||
$null = 'NO';
|
||||
$defaultType = 'USER_DEFINED';
|
||||
$defaultValue = 'UNIX_TIMESTAMP()';
|
||||
$extra = '';
|
||||
$comment = '';
|
||||
$virtuality = '';
|
||||
$expression = '';
|
||||
$moveTo = '';
|
||||
$columnsWithIndex = ['id'];
|
||||
$oldColumnName = 'created_at';
|
||||
|
||||
$query = Table::generateFieldSpec(
|
||||
$name,
|
||||
$type,
|
||||
$length,
|
||||
$attribute,
|
||||
$collation,
|
||||
$null,
|
||||
$defaultType,
|
||||
$defaultValue,
|
||||
$extra,
|
||||
$comment,
|
||||
$virtuality,
|
||||
$expression,
|
||||
$moveTo,
|
||||
);
|
||||
self::assertSame('`created_ts` INT(6) NOT NULL DEFAULT UNIX_TIMESTAMP()', $query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for duplicateInfo
|
||||
*/
|
||||
|
||||
@ -23,6 +23,7 @@ use PHPUnit\Framework\Attributes\CoversClass;
|
||||
use ReflectionProperty;
|
||||
|
||||
use function json_encode;
|
||||
use function str_replace;
|
||||
use function time;
|
||||
|
||||
#[CoversClass(UserPreferences::class)]
|
||||
@ -238,6 +239,137 @@ class UserPreferencesTest extends AbstractTestCase
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for save and keep 2FA settings
|
||||
*/
|
||||
public function testSaveAndKeep2FA(): void
|
||||
{
|
||||
$initialConfig = [
|
||||
'2fa' => ['backend' => 'application', 'settings' => ['secret' => 'thisisasecret']],
|
||||
'theme' => 'dark',
|
||||
];
|
||||
$dummyDbi = $this->createDbiDummy();
|
||||
|
||||
$dummyDbi->removeDefaultResults();
|
||||
$dummyDbi->addSelectDb('pma-db');
|
||||
$dummyDbi->addResult(
|
||||
'SHOW TABLES FROM `pma-db`;',
|
||||
[['pma__userconfig']],
|
||||
['Tables_in_pma-db'],
|
||||
);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
'SELECT NULL FROM `pma__userconfig` LIMIT 0',
|
||||
[['NULL']],
|
||||
);
|
||||
|
||||
$dbi = $this->createDatabaseInterface($dummyDbi);
|
||||
$config = new Config();
|
||||
$config->selectedServer['pmadb'] = 'pma-db';
|
||||
$config->selectedServer['userconfig'] = 'pma__userconfig';
|
||||
$relation = new Relation($dbi, $config);
|
||||
(new ReflectionProperty(Relation::class, 'cache'))->setValue(null, null);
|
||||
$relationParameters = $relation->getRelationParameters();
|
||||
self::assertNotNull($relationParameters->userPreferencesFeature);
|
||||
|
||||
$userPreferences = new UserPreferences($dbi, $relation, new Template());
|
||||
|
||||
// phpcs:disable Generic.Files.LineLength.TooLong
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[],
|
||||
);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `username` FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[],
|
||||
);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
INSERT INTO `pma-db`.`pma__userconfig` (`username`, `timevalue`,`config_data`) VALUES ('root', NOW(), '[1]')
|
||||
SQL,
|
||||
[],
|
||||
);
|
||||
|
||||
$result = $userPreferences->save([1]);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `username` FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[],
|
||||
);
|
||||
|
||||
$encodedConfig = json_encode($initialConfig);
|
||||
$encodedEscapedConfig = str_replace('"', '\"', $encodedConfig);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<SQL
|
||||
INSERT INTO `pma-db`.`pma__userconfig` (`username`, `timevalue`,`config_data`) VALUES ('root', NOW(), '$encodedEscapedConfig')
|
||||
SQL,
|
||||
[],
|
||||
);
|
||||
|
||||
// Test 2fa preservation on partial save
|
||||
|
||||
// Initial save with 2fa
|
||||
$userPreferences->save($initialConfig);
|
||||
|
||||
// Not using the session storage
|
||||
self::assertFalse(isset($_SESSION['userconfig']));
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[[$encodedConfig, 1767029179]],
|
||||
['config_data', 'ts'],
|
||||
);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `username` FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[['root']],
|
||||
['username'],
|
||||
);
|
||||
|
||||
// 2FA is combined with the previous config
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
UPDATE `pma-db`.`pma__userconfig` SET `timevalue` = NOW(), `config_data` = '{\"2fa\":{\"backend\":\"application\",\"settings\":{\"secret\":\"thisisasecret\"}},\"theme\":\"dark\",\"Console\\/Mode\":\"collapse\"}' WHERE `username` = 'root'
|
||||
SQL,
|
||||
[['root']],
|
||||
['username'],
|
||||
);
|
||||
|
||||
// Partial save without 2fa
|
||||
$partialConfig = ['Console/Mode' => 'collapse'];
|
||||
$userPreferences->save($partialConfig);
|
||||
|
||||
$encodedConfig = json_encode([...$initialConfig, 'Console/Mode' => 'collapse']);
|
||||
|
||||
$dummyDbi->addResult(
|
||||
<<<'SQL'
|
||||
SELECT `config_data`, UNIX_TIMESTAMP(`timevalue`) ts FROM `pma-db`.`pma__userconfig` WHERE `username` = 'root'
|
||||
SQL,
|
||||
[[$encodedConfig, 1767029179]],
|
||||
['config_data', 'ts'],
|
||||
);
|
||||
|
||||
// Check that 2fa is still present
|
||||
$resultConfig = $userPreferences->load()['config_data'];
|
||||
self::assertSame('thisisasecret', $resultConfig['2fa']['settings']['secret']);
|
||||
self::assertSame('collapse', $resultConfig['Console/Mode']);
|
||||
$dummyDbi->assertAllSelectsConsumed();
|
||||
// phpcs:enable Generic.Files.LineLength.TooLong
|
||||
}
|
||||
|
||||
/**
|
||||
* Test for apply
|
||||
*/
|
||||
|
||||
Loading…
Reference in New Issue
Block a user