diff --git a/psalm-baseline.xml b/psalm-baseline.xml index e780af5f1b..1e6a094da6 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -13620,8 +13620,6 @@ - - config->debug->sql]]> @@ -13758,6 +13756,7 @@ + diff --git a/resources/js/src/modules/functions.ts b/resources/js/src/modules/functions.ts index 342f4d5def..358ab174a7 100644 --- a/resources/js/src/modules/functions.ts +++ b/resources/js/src/modules/functions.ts @@ -1763,6 +1763,29 @@ function sqlPrettyPrint (string) { return output; } +function createFunctionConfirmModal (): void { + if ($('#functionConfirmModal').length > 0) { + return; + } + + const functionConfirmModalTemplate = ''; + $(functionConfirmModalTemplate).appendTo('body'); +} + /** * jQuery function that uses jQueryUI's dialogs to confirm with user. Does not * return a jQuery object yet and hence cannot be chained @@ -1790,6 +1813,8 @@ function confirmDialog (question, url = undefined, callbackFn = undefined, openC return true; } + createFunctionConfirmModal(); + const functionConfirmModal = $('#functionConfirmModal') as JQuery; functionConfirmModal.modal('show'); functionConfirmModal.find('.modal-body').first().html(question); diff --git a/resources/js/src/modules/indexes.ts b/resources/js/src/modules/indexes.ts index f18e775361..c9671365d1 100644 --- a/resources/js/src/modules/indexes.ts +++ b/resources/js/src/modules/indexes.ts @@ -322,6 +322,8 @@ function showAddIndexDialog (sourceArray, arrayIndex, targetColumns, colIndex, i postData.columns = JSON.stringify(columns); + createAddIndexModal(); + $('#addIndexModalGoButton').on('click', function () { addIndexGo(sourceArray, arrayIndex, index, colIndex); }); @@ -447,6 +449,8 @@ function indexTypeSelectionDialog (sourceArray, indexChoice, colIndex): void { $dialogContent.append($singleColumnRadio); $dialogContent.append($compositeIndexRadio); + createAddIndexModal(); + // 'OK' operation. $('#addIndexModalGoButton').on('click', function () { if ($('#single_column').is(':checked')) { @@ -543,6 +547,29 @@ function off () { }; } +function createAddIndexModal (): void { + if ($('#addIndexModal').length > 0) { + return; + } + + const addIndexModalTemplate = ''; + $(addIndexModalTemplate).appendTo('body'); +} + /** * @return {function} */ diff --git a/resources/js/src/modules/page_settings.ts b/resources/js/src/modules/page_settings.ts index ce6309a51d..a9e39209af 100644 --- a/resources/js/src/modules/page_settings.ts +++ b/resources/js/src/modules/page_settings.ts @@ -1,5 +1,28 @@ import $ from 'jquery'; +function createPageSettingsModal (): void { + if ($('#pageSettingsModal').length > 0) { + return; + } + + const pageSettingsModalTemplate = ''; + $(pageSettingsModalTemplate).appendTo('body'); +} + /** * @fileoverview function used for page-related settings * @name Page-related settings @@ -8,6 +31,8 @@ import $ from 'jquery'; */ function showSettings (selector) { + createPageSettingsModal(); + // Keeping a clone to restore in case the user cancels the operation var $clone = $(selector + ' .page_settings').clone(true); diff --git a/resources/templates/base.twig b/resources/templates/base.twig index 65774b3ff6..2a129c259f 100644 --- a/resources/templates/base.twig +++ b/resources/templates/base.twig @@ -1 +1,170 @@ -{{- header|raw -}}{{- content|raw -}}{{- footer|raw -}} + + + + + + + + + + {% if not header.allow_third_party_framing -%} + + {%- endif %} + + + + + + + + + {{ header.title }} + {{ header.scripts|raw }} + + + +{% endif %} + +{% if header.is_menu_enabled and header.server > 0 %} + {{ header.menu|raw }} + +{% endif %} + +{{ header.console|raw }} + +
+ {{ header.messages|raw }} + + {%- if header.is_logged_in -%} + {{ include('modals/preview_sql_modal.twig') }} + {{ include('modals/enum_set_editor.twig') }} + {{ include('modals/create_view.twig') }} + {%- endif -%} + +{{ content|raw }} +
+ +{% if not footer.is_minimal %} + {% if footer.self_url is not empty %} + + {% endif %} + +
+ {{ footer.error_messages|raw }} +
+ + {{ footer.scripts|raw }} + + {% if footer.is_demo %} +
+ {% apply notice %} + {{ t('phpMyAdmin Demo Server') }}: + {% if footer.git_revision_info is not empty %} + {% set revision_info -%} + {{ footer.git_revision_info.revision }} + {%- endset %} + {% set branch_info -%} + {{ footer.git_revision_info.branch }} + {%- endset %} + {{ t('Currently running Git revision %1$s from the %2$s branch.')|format(revision_info, branch_info)|raw }} + {% else %} + {{ t('Git information missing!') }} + {% endif %} + {% endapply %} +
+ {% endif %} + + {{ footer.footer|raw }} +{% endif %} + + diff --git a/resources/templates/footer.twig b/resources/templates/footer.twig deleted file mode 100644 index f9c920c698..0000000000 --- a/resources/templates/footer.twig +++ /dev/null @@ -1,50 +0,0 @@ -{{ include('modals/function_confirm.twig') }} -{{ include('modals/add_index.twig') }} -{{ include('modals/page_settings.twig') }} -{% if not is_ajax %} - -{% endif %} -{% if not is_ajax and not is_minimal %} - {% if self_url is not empty %} - - {% endif %} - -
- {{ error_messages|raw }} -
- - {{ scripts|raw }} - - {% if is_demo %} -
- {% apply notice %} - {{ t('phpMyAdmin Demo Server') }}: - {% if git_revision_info is not empty %} - {% set revision_info -%} - {{ git_revision_info.revision }} - {%- endset %} - {% set branch_info -%} - {{ git_revision_info.branch }} - {%- endset %} - {{ t('Currently running Git revision %1$s from the %2$s branch.')|format(revision_info, branch_info)|raw }} - {% else %} - {{ t('Git information missing!') }} - {% endif %} - {% endapply %} -
- {% endif %} - - {{ footer|raw }} -{% endif %} -{% if not is_ajax %} - - -{% endif %} diff --git a/resources/templates/header.twig b/resources/templates/header.twig deleted file mode 100644 index b92dc5a34f..0000000000 --- a/resources/templates/header.twig +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - {% if not allow_third_party_framing -%} - - {%- endif %} - - - - - - - - - {{ title }} - {{ scripts|raw }} - - - - {% endif %} - - {% if is_menu_enabled and server > 0 %} - {{ menu|raw }} - - {% endif %} - - {{ console|raw }} - -
- {{ messages|raw }} - - {%- if is_logged_in -%} - {{ include('modals/preview_sql_modal.twig') }} - {{ include('modals/enum_set_editor.twig') }} - {{ include('modals/create_view.twig') }} - {%- endif -%} diff --git a/resources/templates/modals/add_index.twig b/resources/templates/modals/add_index.twig deleted file mode 100644 index 75c0036b38..0000000000 --- a/resources/templates/modals/add_index.twig +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/resources/templates/modals/function_confirm.twig b/resources/templates/modals/function_confirm.twig deleted file mode 100644 index a22d17eb39..0000000000 --- a/resources/templates/modals/function_confirm.twig +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/resources/templates/modals/page_settings.twig b/resources/templates/modals/page_settings.twig deleted file mode 100644 index 95ea17eb40..0000000000 --- a/resources/templates/modals/page_settings.twig +++ /dev/null @@ -1,15 +0,0 @@ - diff --git a/src/Console.php b/src/Console.php index d4be6fcbaf..0026866905 100644 --- a/src/Console.php +++ b/src/Console.php @@ -25,10 +25,6 @@ class Console */ private bool $isEnabled = true; - /** - * Whether we are servicing an ajax request. - */ - private bool $isAjax = false; private readonly Config $config; public function __construct( @@ -39,17 +35,6 @@ class Console $this->config = Config::getInstance(); } - /** - * Set the ajax flag to indicate whether - * we are servicing an ajax request - * - * @param bool $isAjax Whether we are servicing an ajax request - */ - public function setAjax(bool $isAjax): void - { - $this->isAjax = $isAjax; - } - /** * Disables the rendering of the footer */ @@ -104,7 +89,7 @@ class Console */ public function getDisplay(): string { - if ($this->isAjax || ! $this->isEnabled) { + if (! $this->isEnabled) { return ''; } diff --git a/src/Controllers/JavaScriptMessagesController.php b/src/Controllers/JavaScriptMessagesController.php index 0e79413efe..8b336326b6 100644 --- a/src/Controllers/JavaScriptMessagesController.php +++ b/src/Controllers/JavaScriptMessagesController.php @@ -359,6 +359,7 @@ final class JavaScriptMessagesController implements InvocableController 'strAddingPrimaryKey' => __('Adding primary key'), 'strOK' => __('OK'), 'strDismiss' => __('Click to dismiss this notification'), + 'strConfirm' => __('Confirm'), /* For database/operations.js */ 'strRenamingDatabases' => __('Renaming databases'), diff --git a/src/Footer.php b/src/Footer.php index ad46922752..29162531cc 100644 --- a/src/Footer.php +++ b/src/Footer.php @@ -28,19 +28,16 @@ class Footer * Scripts instance */ private Scripts $scripts; - /** - * Whether we are servicing an ajax request. - */ - private bool $isAjax = false; + /** * Whether to only close the BODY and HTML tags * or also include scripts, errors and links */ private bool $isMinimal = false; - public function __construct(private readonly Template $template, private readonly Config $config) + public function __construct(Template $template, private readonly Config $config) { - $this->scripts = new Scripts($this->template); + $this->scripts = new Scripts($template); } /** @@ -151,17 +148,6 @@ class Footer return $retval; } - /** - * Set the ajax flag to indicate whether - * we are servicing an ajax request - * - * @param bool $isAjax Whether we are servicing an ajax request - */ - public function setAjax(bool $isAjax): void - { - $this->isAjax = $isAjax; - } - /** * Turn on minimal display mode */ @@ -180,12 +166,10 @@ class Footer return $this->scripts; } - /** - * Renders the footer - */ - public function getDisplay(): string + /** @return mixed[] */ + public function getDisplay(): array { - if (! $this->isAjax && ! $this->isMinimal) { + if (! $this->isMinimal) { if (Core::getEnv('SCRIPT_NAME') !== '') { $url = $this->getSelfUrl(); } @@ -201,8 +185,7 @@ class Footer $footer = Config::renderFooter(); } - return $this->template->render('footer', [ - 'is_ajax' => $this->isAjax, + return [ 'is_minimal' => $this->isMinimal, 'self_url' => $url ?? null, 'error_messages' => $errorMessages ?? '', @@ -210,6 +193,6 @@ class Footer 'is_demo' => $this->config->config->debug->demo, 'git_revision_info' => $gitRevisionInfo ?? [], 'footer' => $footer ?? '', - ]); + ]; } } diff --git a/src/Header.php b/src/Header.php index aae74e0218..ce1498130a 100644 --- a/src/Header.php +++ b/src/Header.php @@ -55,15 +55,6 @@ class Header * Whether to show the warnings */ private bool $warningsEnabled = true; - /** - * Whether we are servicing an ajax request. - */ - private bool $isAjax = false; - /** - * Whether the HTTP headers (and possibly some HTML) - * have already been sent to the browser - */ - private bool $headerIsSent = false; private UserPreferences $userPreferences; @@ -159,18 +150,6 @@ class Header return 'window.Navigation.update(window.CommonParams.setAll(' . json_encode($params, JSON_HEX_TAG) . '));'; } - /** - * Set the ajax flag to indicate whether - * we are servicing an ajax request - * - * @param bool $isAjax Whether we are servicing an ajax request - */ - public function setAjax(bool $isAjax): void - { - $this->isAjax = $isAjax; - $this->console->setAjax($isAjax); - } - /** * Returns the Scripts object * @@ -228,17 +207,9 @@ class Header $this->warningsEnabled = false; } - /** - * Generates the header - * - * @return string The header - */ - public function getDisplay(): string + /** @return mixed[] */ + public function getDisplay(): array { - if ($this->headerIsSent || $this->isAjax) { - return ''; - } - $this->sendHttpHeaders(); $baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : ''; @@ -309,7 +280,7 @@ class Header $this->scripts->addFile('datetimepicker.js'); $this->scripts->addFile('validator-messages.js'); - return $this->template->render('header', [ + return [ 'lang' => $GLOBALS['lang'], 'allow_third_party_framing' => $this->config->settings['AllowThirdPartyFraming'], 'base_dir' => $baseDir, @@ -335,7 +306,7 @@ class Header 'theme_id' => $theme->getId(), 'current_user' => $dbi->getCurrentUserAndHost(), 'is_mariadb' => $dbi->isMariaDB(), - ]); + ]; } /** @@ -386,8 +357,6 @@ class Header foreach ($headers as $name => $value) { header(sprintf('%s: %s', $name, $value)); } - - $this->headerIsSent = true; } /** @return array */ diff --git a/src/ResponseRenderer.php b/src/ResponseRenderer.php index bba787b2ce..a4b270c5f5 100644 --- a/src/ResponseRenderer.php +++ b/src/ResponseRenderer.php @@ -165,8 +165,6 @@ class ResponseRenderer public function setAjax(bool $isAjax): void { $this->isAjax = $isAjax; - $this->header->setAjax($this->isAjax); - $this->footer->setAjax($this->isAjax); } /** @@ -242,10 +240,6 @@ class ResponseRenderer */ private function getDisplay(): string { - // The header may contain nothing at all, - // if its content was already rendered - // and, in this case, the header will be - // in the content part of the request return $this->template->render('base', [ 'header' => $this->header->getDisplay(), 'content' => $this->HTML, @@ -259,7 +253,7 @@ class ResponseRenderer private function ajaxResponse(): string { if (! isset($this->JSON['message'])) { - $this->JSON['message'] = $this->getDisplay(); + $this->JSON['message'] = $this->HTML; } elseif ($this->JSON['message'] instanceof Message) { $this->JSON['message'] = $this->JSON['message']->getDisplay(); } diff --git a/tests/unit/ConsoleTest.php b/tests/unit/ConsoleTest.php index 51c4362c92..f870228550 100644 --- a/tests/unit/ConsoleTest.php +++ b/tests/unit/ConsoleTest.php @@ -10,7 +10,6 @@ use PhpMyAdmin\Console; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Template; use PHPUnit\Framework\Attributes\CoversClass; -use ReflectionProperty; #[CoversClass(Console::class)] class ConsoleTest extends AbstractTestCase @@ -24,19 +23,4 @@ class ConsoleTest extends AbstractTestCase $console = new Console($relation, new Template(), $bookmarkRepository); self::assertSame(['console.js'], $console->getScripts()); } - - public function testSetAjax(): void - { - $isAjax = new ReflectionProperty(Console::class, 'isAjax'); - $dbi = $this->createDatabaseInterface(); - $relation = new Relation($dbi); - $bookmarkRepository = new BookmarkRepository($dbi, $relation); - $console = new Console($relation, new Template(), $bookmarkRepository); - - self::assertFalse($isAjax->getValue($console)); - $console->setAjax(true); - self::assertTrue($isAjax->getValue($console)); - $console->setAjax(false); - self::assertFalse($isAjax->getValue($console)); - } } diff --git a/tests/unit/FooterTest.php b/tests/unit/FooterTest.php index 69cbfe7f04..9030edb404 100644 --- a/tests/unit/FooterTest.php +++ b/tests/unit/FooterTest.php @@ -12,7 +12,6 @@ use PhpMyAdmin\Footer; use PhpMyAdmin\Template; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\Group; -use ReflectionProperty; use function json_encode; @@ -100,19 +99,6 @@ class FooterTest extends AbstractTestCase ); } - public function testGetDisplayWhenAjaxIsEnabled(): void - { - $template = new Template(); - $footer = new Footer($template, Config::getInstance()); - $footer->setAjax(true); - self::assertSame( - $template->render('modals/function_confirm') . "\n" - . $template->render('modals/add_index') . "\n" - . $template->render('modals/page_settings') . "\n", - $footer->getDisplay(), - ); - } - /** * Test for footer get Scripts */ @@ -132,10 +118,27 @@ class FooterTest extends AbstractTestCase public function testDisplay(): void { $footer = new Footer(new Template(), Config::getInstance()); - self::assertStringContainsString( - 'Open new phpMyAdmin window', - $footer->getDisplay(), - ); + $scripts = <<<'HTML' + + + + HTML; + + $expected = [ + 'is_minimal' => false, + 'self_url' => 'index.php?route=%2F&server=1&lang=en', + 'error_messages' => '', + 'scripts' => $scripts, + 'is_demo' => false, + 'git_revision_info' => [], + 'footer' => '', + ]; + self::assertSame($expected, $footer->getDisplay()); } /** @@ -146,24 +149,15 @@ class FooterTest extends AbstractTestCase $template = new Template(); $footer = new Footer($template, Config::getInstance()); $footer->setMinimal(); - self::assertSame( - $template->render('modals/function_confirm') . "\n" - . $template->render('modals/add_index') . "\n" - . $template->render('modals/page_settings') - . "\n
\n \n\n", - $footer->getDisplay(), - ); - } - - public function testSetAjax(): void - { - $isAjax = new ReflectionProperty(Footer::class, 'isAjax'); - $footer = new Footer(new Template(), Config::getInstance()); - - self::assertFalse($isAjax->getValue($footer)); - $footer->setAjax(true); - self::assertTrue($isAjax->getValue($footer)); - $footer->setAjax(false); - self::assertFalse($isAjax->getValue($footer)); + $expected = [ + 'is_minimal' => true, + 'self_url' => null, + 'error_messages' => '', + 'scripts' => '', + 'is_demo' => false, + 'git_revision_info' => [], + 'footer' => '', + ]; + self::assertSame($expected, $footer->getDisplay()); } } diff --git a/tests/unit/HeaderTest.php b/tests/unit/HeaderTest.php index 097637a77d..ac63c41465 100644 --- a/tests/unit/HeaderTest.php +++ b/tests/unit/HeaderTest.php @@ -12,6 +12,7 @@ use PhpMyAdmin\Current; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Header; use PhpMyAdmin\Template; +use PhpMyAdmin\Version; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\Medium; @@ -64,30 +65,52 @@ class HeaderTest extends AbstractTestCase ); } - /** - * Test for enable - */ public function testEnable(): void { Current::$server = 0; - $header = $this->getNewHeaderInstance(); - self::assertStringContainsString( - 'phpMyAdmin', - $header->getDisplay(), - ); - } + $GLOBALS['message'] = ''; + $config = Config::getInstance(); + $config->settings['CodemirrorEnable'] = false; + $config->settings['SendErrorReports'] = 'never'; + $config->settings['enable_drag_drop_import'] = false; + $config->settings['DisableShortcutKeys'] = true; + $dbi = $this->createDatabaseInterface(); + DatabaseInterface::$instance = $dbi; + $relation = new Relation($dbi); + $template = new Template($config); + $console = new Console($relation, $template, new BookmarkRepository($dbi, $relation)); + $header = new Header($template, $console, $config); - /** - * Test for Set BodyId - */ - public function testSetBodyId(): void - { - $header = $this->getNewHeaderInstance(); $header->setBodyId('PMA_header_id'); - self::assertStringContainsString( - 'PMA_header_id', - $header->getDisplay(), - ); + $actual = $header->getDisplay(); + $expected = [ + 'lang' => 'en', + 'allow_third_party_framing' => false, + 'base_dir' => '', + 'theme_path' => '', + 'version' => 'v=' . Version::VERSION, + 'text_dir' => 'ltr', + 'server' => 0, + 'title' => 'phpMyAdmin', + 'scripts' => $header->getScripts()->getDisplay(), + 'body_id' => 'PMA_header_id', + 'navigation' => '', + 'custom_header' => '', + 'load_user_preferences' => '', + 'show_hint' => true, + 'is_warnings_enabled' => true, + 'is_menu_enabled' => true, + 'is_logged_in' => true, + 'menu' => '', + 'console' => $console->getDisplay(), + 'messages' => '', + 'theme_color_mode' => 'light', + 'theme_color_modes' => ['light'], + 'theme_id' => '', + 'current_user' => ['pma_test', 'localhost'], + 'is_mariadb' => false, + ]; + self::assertSame($expected, $actual); } /** @@ -269,22 +292,4 @@ class HeaderTest extends AbstractTestCase ]; self::assertSame($expected, $scripts->getFiles()); } - - public function testSetAjax(): void - { - $header = $this->getNewHeaderInstance(); - $console = (new ReflectionProperty(Header::class, 'console'))->getValue($header); - self::assertInstanceOf(Console::class, $console); - $isAjax = new ReflectionProperty(Header::class, 'isAjax'); - $consoleIsAjax = new ReflectionProperty(Console::class, 'isAjax'); - - self::assertFalse($isAjax->getValue($header)); - self::assertFalse($consoleIsAjax->getValue($console)); - $header->setAjax(true); - self::assertTrue($isAjax->getValue($header)); - self::assertTrue($consoleIsAjax->getValue($console)); - $header->setAjax(false); - self::assertFalse($isAjax->getValue($header)); - self::assertFalse($consoleIsAjax->getValue($console)); - } } diff --git a/tests/unit/ResponseRendererTest.php b/tests/unit/ResponseRendererTest.php index fd2a2b3f44..cc639cac8e 100644 --- a/tests/unit/ResponseRendererTest.php +++ b/tests/unit/ResponseRendererTest.php @@ -6,15 +6,12 @@ namespace PhpMyAdmin\Tests; use Fig\Http\Message\StatusCodeInterface; use PhpMyAdmin\DatabaseInterface; -use PhpMyAdmin\Footer; use PhpMyAdmin\Header; use PhpMyAdmin\Html\MySQLDocumentation; use PhpMyAdmin\Message; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Scripts; use PHPUnit\Framework\Attributes\CoversClass; -use PHPUnit\Framework\Attributes\PreserveGlobalState; -use PHPUnit\Framework\Attributes\RunInSeparateProcess; use ReflectionProperty; use function array_column; @@ -31,32 +28,16 @@ class ResponseRendererTest extends AbstractTestCase $GLOBALS['lang'] = 'en'; } - #[RunInSeparateProcess] - #[PreserveGlobalState(false)] public function testSetAjax(): void { $_REQUEST = []; (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); $response = ResponseRenderer::getInstance(); - $header = $response->getHeader(); - $footer = (new ReflectionProperty(ResponseRenderer::class, 'footer'))->getValue($response); - self::assertInstanceOf(Footer::class, $footer); - $headerIsAjax = new ReflectionProperty(Header::class, 'isAjax'); - $footerIsAjax = new ReflectionProperty(Footer::class, 'isAjax'); - self::assertFalse($response->isAjax()); - self::assertFalse($headerIsAjax->getValue($header)); - self::assertFalse($footerIsAjax->getValue($footer)); - $response->setAjax(true); self::assertTrue($response->isAjax()); - self::assertTrue($headerIsAjax->getValue($header)); - self::assertTrue($footerIsAjax->getValue($footer)); - $response->setAjax(false); self::assertFalse($response->isAjax()); - self::assertFalse($headerIsAjax->getValue($header)); - self::assertFalse($footerIsAjax->getValue($footer)); (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); }