From c82888a8841296207501053b322820538d4c3653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Fri, 24 May 2024 17:07:45 -0300 Subject: [PATCH 1/2] Extract dependencies from ResponseRenderer constructor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: MaurĂ­cio Meneghini Fauth --- phpstan-baseline.neon | 2 +- psalm-baseline.xml | 2 - src/ResponseRenderer.php | 61 ++++++++++++++------------- tests/unit/Stubs/ResponseRenderer.php | 26 ++++++------ 4 files changed, 46 insertions(+), 45 deletions(-) diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 661ea4338a..dfd522a04d 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -12282,7 +12282,7 @@ parameters: - message: "#^Construct empty\\(\\) is not allowed\\. Use more strict comparison\\.$#" - count: 5 + count: 4 path: src/ResponseRenderer.php - diff --git a/psalm-baseline.xml b/psalm-baseline.xml index 1e6a094da6..9a31f998a0 100644 --- a/psalm-baseline.xml +++ b/psalm-baseline.xml @@ -10159,7 +10159,6 @@ - @@ -10173,7 +10172,6 @@ - diff --git a/src/ResponseRenderer.php b/src/ResponseRenderer.php index a4b270c5f5..67d63be635 100644 --- a/src/ResponseRenderer.php +++ b/src/ResponseRenderer.php @@ -28,10 +28,6 @@ class ResponseRenderer { private static ResponseRenderer|null $instance = null; - /** - * Header instance - */ - protected Header $header; /** * HTML data to be used in the response */ @@ -43,10 +39,7 @@ class ResponseRenderer * @var mixed[] */ private array $JSON = []; - /** - * PhpMyAdmin\Footer instance - */ - protected Footer $footer; + /** * Whether we are servicing an ajax request. */ @@ -136,24 +129,16 @@ class ResponseRenderer protected Response $response; - protected Template $template; - protected Config $config; - - private function __construct() - { - $this->config = Config::getInstance(); - $this->template = new Template(); - $dbi = DatabaseInterface::getInstance(); - $relation = new Relation($dbi); - $this->header = new Header( - $this->template, - new Console($relation, $this->template, new BookmarkRepository($dbi, $relation)), - $this->config, - ); - $this->footer = new Footer($this->template, $this->config); - $this->response = ResponseFactory::create()->createResponse(); - - $this->setAjax(! empty($_REQUEST['ajax_request'])); + protected function __construct( + protected Config $config, + protected Template $template, + protected Header $header, + protected Footer $footer, + protected ErrorHandler $errorHandler, + protected DatabaseInterface $dbi, + ResponseFactory $responseFactory, + ) { + $this->response = $responseFactory->createResponse(StatusCodeInterface::STATUS_OK, 'OK'); } /** @@ -172,10 +157,26 @@ class ResponseRenderer */ public static function getInstance(): ResponseRenderer { - if (self::$instance === null) { - self::$instance = new ResponseRenderer(); + if (self::$instance !== null) { + return self::$instance; } + $config = Config::getInstance(); + $template = new Template($config); + $dbi = DatabaseInterface::getInstance(); + $relation = new Relation($dbi); + $console = new Console($relation, $template, new BookmarkRepository($dbi, $relation)); + + self::$instance = new ResponseRenderer( + $config, + $template, + new Header($template, $console, $config), + new Footer($template, $config), + ErrorHandler::getInstance(), + $dbi, + ResponseFactory::create(), + ); + return self::$instance; } @@ -271,7 +272,7 @@ class ResponseRenderer $this->addJSON('title', '' . $this->getHeader()->getPageTitle() . ''); } - if (DatabaseInterface::getInstance()->isConnected()) { + if ($this->dbi->isConnected()) { $this->addJSON('menu', $this->getHeader()->getMenu()->getDisplay()); } @@ -289,7 +290,7 @@ class ResponseRenderer $this->addJSON('errors', $errors); } - $promptPhpErrors = ErrorHandler::getInstance()->hasErrorsForPrompt(); + $promptPhpErrors = $this->errorHandler->hasErrorsForPrompt(); $this->addJSON('promptPhpErrors', $promptPhpErrors); if (empty($GLOBALS['error_message'])) { diff --git a/tests/unit/Stubs/ResponseRenderer.php b/tests/unit/Stubs/ResponseRenderer.php index d51daab7d3..e073928eca 100644 --- a/tests/unit/Stubs/ResponseRenderer.php +++ b/tests/unit/Stubs/ResponseRenderer.php @@ -16,6 +16,7 @@ use PhpMyAdmin\Config; use PhpMyAdmin\ConfigStorage\Relation; use PhpMyAdmin\Console; use PhpMyAdmin\DatabaseInterface; +use PhpMyAdmin\Error\ErrorHandler; use PhpMyAdmin\Footer; use PhpMyAdmin\Header; use PhpMyAdmin\Http\Factory\ResponseFactory; @@ -46,24 +47,25 @@ class ResponseRenderer extends \PhpMyAdmin\ResponseRenderer */ public function __construct() { - $this->isSuccess = true; - $this->isAjax = false; - $GLOBALS['lang'] = 'en'; - $this->template = new Template(); - $this->config = Config::getInstance(); - $this->config->selectedServer['pmadb'] = 'phpmyadmin'; + $config = Config::getInstance(); + $config->selectedServer['pmadb'] = 'phpmyadmin'; + $template = new Template($config); $dummyDbi = new DbiDummy(); $dummyDbi->addSelectDb('phpmyadmin'); $dbi = new DatabaseInterface($dummyDbi); $relation = new Relation($dbi); - $this->header = new Header( - $this->template, - new Console($relation, $this->template, new BookmarkRepository($dbi, $relation)), - $this->config, + $console = new Console($relation, $template, new BookmarkRepository($dbi, $relation)); + + parent::__construct( + $config, + $template, + new Header($template, $console, $config), + new Footer($template, $config), + ErrorHandler::getInstance(), + $dbi, + ResponseFactory::create(), ); - $this->footer = new Footer($this->template, $this->config); - $this->response = ResponseFactory::create()->createResponse(); } /** From ebf0f11d2d49d84f9adb98583593b214056b01d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maur=C3=ADcio=20Meneghini=20Fauth?= Date: Sat, 25 May 2024 14:16:11 -0300 Subject: [PATCH 2/2] Refactor ResponseRenderer::response() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds HTTP headers to the Response object instead of using the header() function directly. Signed-off-by: MaurĂ­cio Meneghini Fauth --- src/Header.php | 30 ++----- src/ResponseRenderer.php | 20 +++-- tests/unit/ResponseRendererTest.php | 118 +++++++++++++++++++++++++++- 3 files changed, 134 insertions(+), 34 deletions(-) diff --git a/src/Header.php b/src/Header.php index ce1498130a..a86a54c7d6 100644 --- a/src/Header.php +++ b/src/Header.php @@ -15,8 +15,6 @@ use PhpMyAdmin\Theme\ThemeManager; use function array_merge; use function defined; -use function gmdate; -use function header; use function htmlspecialchars; use function ini_get; use function json_encode; @@ -210,8 +208,6 @@ class Header /** @return mixed[] */ public function getDisplay(): array { - $this->sendHttpHeaders(); - $baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : ''; /** @var ThemeManager $themeManager */ @@ -338,27 +334,6 @@ class Header return $retval; } - /** - * Sends out the HTTP headers - */ - public function sendHttpHeaders(): void - { - if (defined('TESTSUITE')) { - return; - } - - /** - * Sends http headers - */ - $GLOBALS['now'] = gmdate('D, d M Y H:i:s') . ' GMT'; - - $headers = $this->getHttpHeaders(); - - foreach ($headers as $name => $value) { - header(sprintf('%s: %s', $name, $value)); - } - } - /** @return array */ public function getHttpHeaders(): array { @@ -545,4 +520,9 @@ class Header { $this->isTransformationWrapper = $isTransformationWrapper; } + + public function getConsole(): Console + { + return $this->console; + } } diff --git a/src/ResponseRenderer.php b/src/ResponseRenderer.php index 67d63be635..6c57c1b929 100644 --- a/src/ResponseRenderer.php +++ b/src/ResponseRenderer.php @@ -318,12 +318,6 @@ class ResponseRenderer } } - // Set the Content-Type header to JSON so that jQuery parses the - // response correctly. - foreach (Core::headerJSON() as $name => $value) { - $this->addHeader($name, $value); - } - $result = json_encode($this->JSON); if ($result === false) { return (string) json_encode([ @@ -337,9 +331,19 @@ class ResponseRenderer public function response(): Response { - $this->response->getBody()->write($this->isAjax() ? $this->ajaxResponse() : $this->getDisplay()); + if ($this->isAjax()) { + $headers = Core::headerJSON(); + $body = $this->ajaxResponse(); + } else { + $headers = $this->header->getHttpHeaders(); + $body = $this->getDisplay(); + } - return $this->response; + foreach ($headers as $name => $value) { + $this->response = $this->response->withHeader($name, $value); + } + + return $this->response->write($body); } public function addHeader(string $name, string $value): void diff --git a/tests/unit/ResponseRendererTest.php b/tests/unit/ResponseRendererTest.php index cc639cac8e..7b3abb1de0 100644 --- a/tests/unit/ResponseRendererTest.php +++ b/tests/unit/ResponseRendererTest.php @@ -5,19 +5,24 @@ declare(strict_types=1); namespace PhpMyAdmin\Tests; use Fig\Http\Message\StatusCodeInterface; +use PhpMyAdmin\Config; +use PhpMyAdmin\Current; use PhpMyAdmin\DatabaseInterface; use PhpMyAdmin\Header; use PhpMyAdmin\Html\MySQLDocumentation; use PhpMyAdmin\Message; use PhpMyAdmin\ResponseRenderer; use PhpMyAdmin\Scripts; +use PhpMyAdmin\Template; +use PhpMyAdmin\Version; use PHPUnit\Framework\Attributes\CoversClass; use ReflectionProperty; use function array_column; +use function json_decode; #[CoversClass(ResponseRenderer::class)] -class ResponseRendererTest extends AbstractTestCase +final class ResponseRendererTest extends AbstractTestCase { protected function setUp(): void { @@ -107,4 +112,115 @@ class ResponseRendererTest extends AbstractTestCase (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); } + + public function testHtmlResponse(): void + { + (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); + + $_SERVER['SCRIPT_NAME'] = 'index.php'; + Current::$server = 0; + + $responseRenderer = ResponseRenderer::getInstance(); + $responseRenderer->setAjax(false); + $responseRenderer->addHTML('
TEST
'); + + $response = $responseRenderer->response(); + + self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode()); + self::assertSame('text/html; charset=utf-8', $response->getHeaderLine('Content-Type')); + $header = $responseRenderer->getHeader(); + self::assertSame( + (new Template(new Config()))->render('base', [ + 'header' => [ + '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' => '', + 'navigation' => '', + 'custom_header' => '', + 'load_user_preferences' => '', + 'show_hint' => true, + 'is_warnings_enabled' => true, + 'is_menu_enabled' => true, + 'is_logged_in' => true, + 'menu' => '', + 'console' => $header->getConsole()->getDisplay(), + 'messages' => '', + 'theme_color_mode' => 'light', + 'theme_color_modes' => ['light'], + 'theme_id' => '', + 'current_user' => ['pma_test', 'localhost'], + 'is_mariadb' => false, + ], + 'content' => '
TEST
', + 'footer' => [ + 'is_minimal' => false, + 'self_url' => 'index.php?route=%2F&server=0&lang=en', + 'error_messages' => '', + 'scripts' => <<<'HTML' + + + + HTML, + 'is_demo' => false, + 'git_revision_info' => [], + 'footer' => '', + ], + ]), + (string) $response->getBody(), + ); + + (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); + } + + public function testJsonResponse(): void + { + (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); + + $_SERVER['SCRIPT_NAME'] = 'index.php'; + Current::$server = 0; + + $responseRenderer = ResponseRenderer::getInstance(); + $responseRenderer->setAjax(true); + $responseRenderer->addJSON('message', 'test message'); + $responseRenderer->addJSON('test', 'test'); + + $response = $responseRenderer->response(); + + self::assertSame(StatusCodeInterface::STATUS_OK, $response->getStatusCode()); + self::assertSame('application/json; charset=UTF-8', $response->getHeaderLine('Content-Type')); + $body = (string) $response->getBody(); + self::assertJson($body); + $header = $responseRenderer->getHeader(); + self::assertEquals( + [ + 'message' => 'test message', + 'test' => 'test', + 'success' => true, + 'title' => 'phpMyAdmin', + 'menu' => $header->getMenu()->getDisplay(), + 'scripts' => $header->getScripts()->getFiles(), + 'selflink' => 'index.php?route=%2F&server=0&lang=en', + 'displayMessage' => '', + 'debug' => "'false'", + 'promptPhpErrors' => false, + 'reloadQuerywindow' => ['db' => '', 'table' => '', 'sql_query' => ''], + 'params' => $header->getJsParams(), + ], + json_decode($body, true), + ); + + (new ReflectionProperty(ResponseRenderer::class, 'instance'))->setValue(null, null); + } }