【閲覧注意】Ubuntu24でphpMyAdminを開けない原因が、ついに迷宮入り!?
前回は上記のリンクまでやり取りしてました。以下のやり取りから続きを投稿します。
質問
[Wed Sep 18 19:50:24.845510 2024] [mpm_prefork:notice] [pid 7408] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:50:24.988632 2024] [mpm_prefork:notice] [pid 7725] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:50:24.988720 2024] [core:notice] [pid 7725] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 20:08:39.295242 2024] [mpm_prefork:notice] [pid 7725] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 20:08:39.424069 2024] [mpm_prefork:notice] [pid 8033] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 20:08:39.424144 2024] [core:notice] [pid 8033] AH00094: Command line: '/usr/sbin/apache2'
なぜまだlocalhostが500のままなのか分かりません。
owner@Linux-for-owner:/usr/share/php$ sudo composer dump-autoload
sudo systemctl restart apache2
[sudo] owner のパスワード:
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Continue as root/super user [yes]?
Generating autoload files
Generated autoload files
[sudo] owner のパスワード:
owner@Linux-for-owner:/usr/share/php$ sudo tail -n 10 /var/log/apache2/error.log[Wed Sep 18 19:27:37.810844 2024] [php:error] [pid 6959] [client 127.0.0.1:44218] PHP Fatal error: Uncaught Error: Call to a member function getDisplay() on null in /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php:293\nStack trace:\n#0 /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php(411): PhpMyAdmin\\ResponseRenderer->getDisplay()\n#1 [internal function]: PhpMyAdmin\\ResponseRenderer->response()\n#2 {main}\n thrown in /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php on line 293
[Wed Sep 18 19:40:00.803499 2024] [mpm_prefork:notice] [pid 6957] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:40:00.917683 2024] [mpm_prefork:notice] [pid 7408] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:40:00.917749 2024] [core:notice] [pid 7408] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 19:50:24.845510 2024] [mpm_prefork:notice] [pid 7408] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:50:24.988632 2024] [mpm_prefork:notice] [pid 7725] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:50:24.988720 2024] [core:notice] [pid 7725] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 20:08:39.295242 2024] [mpm_prefork:notice] [pid 7725] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 20:08:39.424069 2024] [mpm_prefork:notice] [pid 8033] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 20:08:39.424144 2024] [core:notice] [pid 8033] AH00094: Command line: '/usr/sbin/apache2'
owner@Linux-for-owner:/usr/share/php$
回答
今回のエラーログでは、「`getDisplay()` メソッドへの呼び出しが null オブジェクトに対して行われた」ことを示しています。エラーログの詳細を元に、次の手順で原因を特定し修正していきたいと思います。
### 手順1: クラス`ResponseRenderer`の確認
`/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php` ファイルの内容を確認し、エラーログに示されている `getDisplay()` メソッドの部分を確認します。
php
// ファイルの該当部分を探します。
#line 293 が問題の部分です。
// 例:
public function getDisplay()
{
// 必要な処理が行われているかを確認します。
}
### 手順2: 依存クラスの初期化
エラーの大部分は正しく初期化されていないクラスや未設定の変数が原因です。クラスや変数が正しいかを確認します。
### 手順3: MoTranslatorの関連設定
`MoTranslator` パッケージが正しく機能するためには、正しい設定が行われていることを確認します。
php
use PhpMyAdmin\Twig\Extensions\I18nExtension;
// 必要なクラスをインポートし、コードが正しいか再確認します。
### ファイルの内容確認
まず、`/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php` の内容を確認し、クラスの定義やメソッドの問題を特定します。以下を実行して、該当ファイルを確認しましょう:
bash
sudo nano /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
問題の `getDisplay()` メソッドとその呼び出し部分を確認します。
### 手順4: `gettext` 関数が正しく登録されているか確認
特にエラーログに関して利用されている `gettext` 関数が正しく使用されるための設定を次のように確認します:
php
// gettext 関数が正しく登録されているか確認します。
bindtextdomain('phpmyadmin', '/usr/share/phpmyadmin/locale');
textdomain('phpmyadmin');
bind_textdomain_codeset('phpmyadmin', 'UTF-8');
これにより、`gettext` 関連の初期の設定が確認できます。
### ファイル関連設定の確認
次に、確認が必要なファイルの内容を以下に示します:
#### `/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php`
php
public function getDisplay()
{
// 必要な処理が行われているか再度確認します。
}
// 使用の際に getContents メソッドが必要な場合、以下のように対して `getContents()` メソッドの内容を確認します。
$renderer = new ResponseRenderer();
$displayData = $renderer->getContents()->getDisplay();
### まとめ
1. **`ResponseRenderer.php` の内容を再確認**:
bash
sudo nano /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
2. **該当エラー箇所(`getDisplay()` メソッド)の内容を特定し修正**:
php
public function getDisplay()
{
// 必要な処理が行われているか確認します。
// このメソッドにおける不備や間違いを修正することが必要です。
}
3. **`gettext` の初期設定が正しいか確認**:
php
bindtextdomain('phpmyadmin', '/usr/share/phpmyadmin/locale');
textdomain('phpmyadmin');
bind_textdomain_codeset('phpmyadmin', 'UTF-8');
4. **Apacheの再起動および動作確認**:
bash
sudo composer dump-autoload
sudo systemctl restart apache2
sudo tail -n 50 /var/log/apache2/error.log
この手順に従い、エラーの詳細を確認し、修正を行ってください。再度問題が解決しない場合、新たなエラーメッセージを提供していただければ、さらなるサポートを提供いたします。
質問
/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
287-298
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
$retval = $this->header->getDisplay();
$retval .= $this->HTML;
$retval .= $this->footer->getDisplay();
return $retval;
}
/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
<?php
/**
* Manages the rendering of pages in PMA
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use function defined;
use function headers_sent;
use function http_response_code;
use function is_array;
use function is_scalar;
use function json_encode;
use function json_last_error_msg;
use function mb_strlen;
use function register_shutdown_function;
use function strlen;
use const PHP_SAPI;
/**
* Singleton class used to manage the rendering of pages in PMA
*/
class ResponseRenderer
{
/**
* Response instance
*
* @static
* @var ResponseRenderer
*/
private static $instance;
/**
* Header instance
*
* @var Header
*/
protected $header;
/**
* HTML data to be used in the response
*
* @var string
*/
private $HTML;
/**
* An array of JSON key-value pairs
* to be sent back for ajax requests
*
* @var array
*/
private $JSON;
/**
* PhpMyAdmin\Footer instance
*
* @var Footer
*/
protected $footer;
/**
* Whether we are servicing an ajax request.
*
* @var bool
*/
protected $isAjax = false;
/**
* Whether response object is disabled
*
* @var bool
*/
private $isDisabled;
/**
* Whether there were any errors during the processing of the request
* Only used for ajax responses
*
* @var bool
*/
protected $isSuccess;
/**
* @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @var array<int, string>
*/
protected static $httpStatusMessages = [
// Informational
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
103 => 'Early Hints',
// Success
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
// Redirection
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
// Client Error
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
421 => 'Misdirected Request',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Too Early',
426 => 'Upgrade Required',
427 => 'Unassigned',
428 => 'Precondition Required',
429 => 'Too Many Requests',
430 => 'Unassigned',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
// Server Error
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
509 => 'Unassigned',
510 => 'Not Extended',
511 => 'Network Authentication Required',
];
/**
* Creates a new class instance
*/
private function __construct()
{
if (! defined('TESTSUITE')) {
$buffer = OutputBuffering::getInstance();
$buffer->start();
register_shutdown_function([$this, 'response']);
}
$this->header = new Header();
$this->HTML = '';
$this->JSON = [];
$this->footer = new Footer();
$this->isSuccess = true;
$this->isDisabled = false;
$this->setAjax(! empty($_REQUEST['ajax_request']));
}
/**
* 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->header->setAjax($this->isAjax);
$this->footer->setAjax($this->isAjax);
}
/**
* Returns the singleton Response object
*
* @return ResponseRenderer object
*/
public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new ResponseRenderer();
}
return self::$instance;
}
/**
* Set the status of an ajax response,
* whether it is a success or an error
*
* @param bool $state Whether the request was successfully processed
*/
public function setRequestStatus(bool $state): void
{
$this->isSuccess = ($state === true);
}
/**
* Returns true or false depending on whether
* we are servicing an ajax request
*/
public function isAjax(): bool
{
return $this->isAjax;
}
/**
* Disables the rendering of the header
* and the footer in responses
*/
public function disable(): void
{
$this->header->disable();
$this->footer->disable();
$this->isDisabled = true;
}
/**
* Returns a PhpMyAdmin\Header object
*
* @return Header
*/
public function getHeader()
{
return $this->header;
}
/**
* Returns a PhpMyAdmin\Footer object
*
* @return Footer
*/
public function getFooter()
{
return $this->footer;
}
/**
* Append HTML code to the current output buffer
*/
public function addHTML(string $content): void
{
$this->HTML .= $content;
}
/**
* Add JSON code to the response
*
* @param string|int|array $json Either a key (string) or an array or key-value pairs
* @param mixed|null $value Null, if passing an array in $json otherwise
* it's a string value to the key
*/
public function addJSON($json, $value = null): void
{
if (is_array($json)) {
foreach ($json as $key => $value) {
$this->addJSON($key, $value);
}
} elseif ($value instanceof Message) {
$this->JSON[$json] = $value->getDisplay();
} else {
$this->JSON[$json] = $value;
}
}
/**
* Renders the HTML response text
*/
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
$retval = $this->header->getDisplay();
$retval .= $this->HTML;
$retval .= $this->footer->getDisplay();
return $retval;
}
/**
* Sends a JSON response to the browser
*/
private function ajaxResponse(): string
{
global $dbi;
/* Avoid wrapping in case we're disabled */
if ($this->isDisabled) {
return $this->getDisplay();
}
if (! isset($this->JSON['message'])) {
$this->JSON['message'] = $this->getDisplay();
} elseif ($this->JSON['message'] instanceof Message) {
$this->JSON['message'] = $this->JSON['message']->getDisplay();
}
if ($this->isSuccess) {
$this->JSON['success'] = true;
} else {
$this->JSON['success'] = false;
$this->JSON['error'] = $this->JSON['message'];
unset($this->JSON['message']);
}
if ($this->isSuccess) {
if (! isset($this->JSON['title'])) {
$this->addJSON('title', '<title>' . $this->getHeader()->getPageTitle() . '</title>');
}
if (isset($dbi)) {
$this->addJSON('menu', $this->getHeader()->getMenu()->getDisplay());
}
$this->addJSON('scripts', $this->getHeader()->getScripts()->getFiles());
$this->addJSON('selflink', $this->getFooter()->getSelfUrl());
$this->addJSON('displayMessage', $this->getHeader()->getMessage());
$debug = $this->footer->getDebugMessage();
if (empty($_REQUEST['no_debug']) && strlen($debug) > 0) {
$this->addJSON('debug', $debug);
}
$errors = $this->footer->getErrorMessages();
if (strlen($errors) > 0) {
$this->addJSON('errors', $errors);
}
$promptPhpErrors = $GLOBALS['errorHandler']->hasErrorsForPrompt();
$this->addJSON('promptPhpErrors', $promptPhpErrors);
if (empty($GLOBALS['error_message'])) {
// set current db, table and sql query in the querywindow
// (this is for the bottom console)
$query = '';
$maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
if (isset($GLOBALS['sql_query']) && mb_strlen($GLOBALS['sql_query']) < $maxChars) {
$query = $GLOBALS['sql_query'];
}
$this->addJSON(
'reloadQuerywindow',
[
'db' => isset($GLOBALS['db']) && is_scalar($GLOBALS['db'])
? (string) $GLOBALS['db'] : '',
'table' => isset($GLOBALS['table']) && is_scalar($GLOBALS['table'])
? (string) $GLOBALS['table'] : '',
'sql_query' => $query,
]
);
if (! empty($GLOBALS['focus_querywindow'])) {
$this->addJSON('_focusQuerywindow', $query);
}
if (! empty($GLOBALS['reload'])) {
$this->addJSON('reloadNavigation', 1);
}
$this->addJSON('params', $this->getHeader()->getJsParams());
}
}
// Set the Content-Type header to JSON so that jQuery parses the
// response correctly.
Core::headerJSON();
$result = json_encode($this->JSON);
if ($result === false) {
return (string) json_encode([
'success' => false,
'error' => 'JSON encoding failed: ' . json_last_error_msg(),
]);
}
return $result;
}
/**
* Sends an HTML response to the browser
*/
public function response(): void
{
$buffer = OutputBuffering::getInstance();
if (empty($this->HTML)) {
$this->HTML = $buffer->getContents();
}
if ($this->isAjax()) {
echo $this->ajaxResponse();
} else {
echo $this->getDisplay();
}
$buffer->flush();
exit;
}
/**
* Wrapper around PHP's header() function.
*
* @param string $text header string
*/
public function header($text): void
{
// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
\header($text);
}
/**
* Wrapper around PHP's headers_sent() function.
*/
public function headersSent(): bool
{
return headers_sent();
}
/**
* Wrapper around PHP's http_response_code() function.
*
* @param int $response_code will set the response code.
*/
public function httpResponseCode($response_code): void
{
http_response_code($response_code);
}
/**
* Sets http response code.
*
* @param int $responseCode will set the response code.
*/
public function setHttpResponseCode(int $responseCode): void
{
$this->httpResponseCode($responseCode);
$header = 'status: ' . $responseCode . ' ';
if (isset(static::$httpStatusMessages[$responseCode])) {
$header .= static::$httpStatusMessages[$responseCode];
} else {
$header .= 'Web server is down';
}
if (PHP_SAPI === 'cgi-fcgi') {
return;
}
$this->header($header);
}
/**
* Generate header for 303
*
* @param string $location will set location to redirect.
*/
public function generateHeader303($location): void
{
$this->setHttpResponseCode(303);
$this->header('Location: ' . $location);
if (! defined('TESTSUITE')) {
exit;
}
}
/**
* Configures response for the login page
*
* @return bool Whether caller should exit
*/
public function loginPage(): bool
{
/* Handle AJAX redirection */
if ($this->isAjax()) {
$this->setRequestStatus(false);
// redirect_flag redirects to the login page
$this->addJSON('redirect_flag', '1');
return true;
}
$this->getFooter()->setMinimal();
$header = $this->getHeader();
$header->setBodyId('loginform');
$header->setTitle('phpMyAdmin');
$header->disableMenuAndConsole();
$header->disableWarnings();
return false;
}
}
です。
回答
提供していただいた情報から確認できるところでは、`ResponseRenderer` クラス内の `getDisplay` メソッドが `header` や `footer` のオブジェクトを使用する際に `null` 参照を引き起こしている可能性が高いです。これを解決するために、`header` と `footer` オブジェクトが正しく初期化されているかを確認し、エラーハンドリングを追加します。
### 手順1: `header` と `footer` の初期化確認
まず、`ResponseRenderer` クラスの `__construct` メソッドを確認し、`header` と `footer` が正しく初期化されていることを確認します。既に初期化されているようですが、それを見直して、初期化が確実に行われているか確認します。
php
private function __construct()
{
if (! defined('TESTSUITE')) {
$buffer = OutputBuffering::getInstance();
$buffer->start();
register_shutdown_function([$this, 'response']);
}
$this->header = new Header(); // 確認
$this->HTML = '';
$this->JSON = [];
$this->footer = new Footer(); // 確認
$this->isSuccess = true;
$this->isDisabled = false;
$this->setAjax(! empty($_REQUEST['ajax_request']));
}
### 手順2: エラーハンドリングの追加
次に、`getDisplay` メソッド内での `header` と `footer` の使用部分にエラーハンドリングを追加します。これにより、`null` 参照が発生した場合に適切に処理できるようにします。
php
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
$retval = '';
if ($this->header !== null) {
$retval .= $this->header->getDisplay();
} else {
$retval .= "Header object is null.";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= $this->footer->getDisplay();
} else {
$retval .= "Footer object is null.";
}
return $retval;
}
### 手順3: 依存クラスの確認
さらに、`Header` と `Footer` クラスが正しく存在し、読み込まれているかを確認します。これらが存在しない場合や、他の依存関係が不足している場合は、それらを適切にインストールおよび設定してください。
以下の内容を `/usr/share/phpmyadmin/libraries/classes/Header.php` と `/usr/share/phpmyadmin/libraries/classes/Footer.php` に追加することも検討してください:
php
namespace PhpMyAdmin;
// Header.php の内容確認
class Header
{
public function getDisplay(): string
{
// Header の表示ロジック
return "Header content";
}
public function setAjax(bool $isAjax): void
{
// Ajax の設定ロジック
}
public function disable(): void
{
// Header 無効化ロジック
}
public function getPageTitle(): string
{
return "phpMyAdmin";
}
public function getMenu()
{
return new Menu();
}
public function getScripts()
{
return new Scripts();
}
public function getMessage()
{
return "Some message";
}
public function getJsParams()
{
return [];
}
public function disableMenuAndConsole(): void
{
// メニューとコンソールを無効にするロジック
}
public function disableWarnings(): void
{
// 警告を無効にするロジック
}
public function setBodyId($id): void
{
// Body ID の設定ロジック
}
public function setTitle($title): void
{
// タイトルの設定ロジック
}
}
// Footer.php の内容確認
class Footer
{
public function getDisplay(): string
{
// Footer の表示ロジック
return "Footer content";
}
public function setAjax(bool $isAjax): void
{
// Ajax の設定ロジック
}
public function disable(): void
{
// Footer 無効化ロジック
}
public function getDebugMessage(): string
{
return "Debug message";
}
public function getErrorMessages(): string
{
return "Error messages";
}
public function getSelfUrl(): string
{
return "http://localhost/phpmyadmin";
}
public function setMinimal(): void
{
// Footer を最小化
}
}
### 手順4: Apacheの再起動とエラーログの確認
各手順を実行した後は、変更を適用するためにApacheを再起動し、エラーログを再確認します:
bash
sudo composer dump-autoload
sudo systemctl restart apache2
sudo tail -n 50 /var/log/apache2/error.log
これにより、`null` 参照の問題が解決され、適切にエラーハンドリングされた状態でphpMyAdminが正常に動作することを期待しています。再度問題が発生した場合、具体的なエラーメッセージを提供してください。さらなるサポートを提供いたします。
質問
/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
<?php
/**
* Manages the rendering of pages in PMA
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use function defined;
use function headers_sent;
use function http_response_code;
use function is_array;
use function is_scalar;
use function json_encode;
use function json_last_error_msg;
use function mb_strlen;
use function register_shutdown_function;
use function strlen;
use const PHP_SAPI;
/**
* Singleton class used to manage the rendering of pages in PMA
*/
class ResponseRenderer
{
/**
* Response instance
*
* @static
* @var ResponseRenderer
*/
private static $instance;
/**
* Header instance
*
* @var Header
*/
protected $header;
/**
* HTML data to be used in the response
*
* @var string
*/
private $HTML;
/**
* An array of JSON key-value pairs
* to be sent back for ajax requests
*
* @var array
*/
private $JSON;
/**
* PhpMyAdmin\Footer instance
*
* @var Footer
*/
protected $footer;
/**
* Whether we are servicing an ajax request.
*
* @var bool
*/
protected $isAjax = false;
/**
* Whether response object is disabled
*
* @var bool
*/
private $isDisabled;
/**
* Whether there were any errors during the processing of the request
* Only used for ajax responses
*
* @var bool
*/
protected $isSuccess;
/**
* @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @var array<int, string>
*/
protected static $httpStatusMessages = [
// Informational
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
103 => 'Early Hints',
// Success
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
// Redirection
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
// Client Error
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
421 => 'Misdirected Request',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Too Early',
426 => 'Upgrade Required',
427 => 'Unassigned',
428 => 'Precondition Required',
429 => 'Too Many Requests',
430 => 'Unassigned',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
// Server Error
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
509 => 'Unassigned',
510 => 'Not Extended',
511 => 'Network Authentication Required',
];
/**
* Creates a new class instance
*/
private function __construct()
{
if (! defined('TESTSUITE')) {
$buffer = OutputBuffering::getInstance();
$buffer->start();
register_shutdown_function([$this, 'response']);
}
$this->header = new Header();
$this->HTML = '';
$this->JSON = [];
$this->footer = new Footer();
$this->isSuccess = true;
$this->isDisabled = false;
$this->setAjax(! empty($_REQUEST['ajax_request']));
}
/**
* 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->header->setAjax($this->isAjax);
$this->footer->setAjax($this->isAjax);
}
/**
* Returns the singleton Response object
*
* @return ResponseRenderer object
*/
public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new ResponseRenderer();
}
return self::$instance;
}
/**
* Set the status of an ajax response,
* whether it is a success or an error
*
* @param bool $state Whether the request was successfully processed
*/
public function setRequestStatus(bool $state): void
{
$this->isSuccess = ($state === true);
}
/**
* Returns true or false depending on whether
* we are servicing an ajax request
*/
public function isAjax(): bool
{
return $this->isAjax;
}
/**
* Disables the rendering of the header
* and the footer in responses
*/
public function disable(): void
{
$this->header->disable();
$this->footer->disable();
$this->isDisabled = true;
}
/**
* Returns a PhpMyAdmin\Header object
*
* @return Header
*/
public function getHeader()
{
return $this->header;
}
/**
* Returns a PhpMyAdmin\Footer object
*
* @return Footer
*/
public function getFooter()
{
return $this->footer;
}
/**
* Append HTML code to the current output buffer
*/
public function addHTML(string $content): void
{
$this->HTML .= $content;
}
/**
* Add JSON code to the response
*
* @param string|int|array $json Either a key (string) or an array or key-value pairs
* @param mixed|null $value Null, if passing an array in $json otherwise
* it's a string value to the key
*/
public function addJSON($json, $value = null): void
{
if (is_array($json)) {
foreach ($json as $key => $value) {
$this->addJSON($key, $value);
}
} elseif ($value instanceof Message) {
$this->JSON[$json] = $value->getDisplay();
} else {
$this->JSON[$json] = $value;
}
}
/**
* Renders the HTML response text
*/
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
$retval = '';
if ($this->header !== null) {
$retval .= $this->header->getDisplay();
} else {
$retval .= "Header object is null.";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= $this->footer->getDisplay();
} else {
$retval .= "Footer object is null.";
}
return $retval;
}
/**
* Sends a JSON response to the browser
*/
private function ajaxResponse(): string
{
global $dbi;
/* Avoid wrapping in case we're disabled */
if ($this->isDisabled) {
return $this->getDisplay();
}
if (! isset($this->JSON['message'])) {
$this->JSON['message'] = $this->getDisplay();
} elseif ($this->JSON['message'] instanceof Message) {
$this->JSON['message'] = $this->JSON['message']->getDisplay();
}
if ($this->isSuccess) {
$this->JSON['success'] = true;
} else {
$this->JSON['success'] = false;
$this->JSON['error'] = $this->JSON['message'];
unset($this->JSON['message']);
}
if ($this->isSuccess) {
if (! isset($this->JSON['title'])) {
$this->addJSON('title', '<title>' . $this->getHeader()->getPageTitle() . '</title>');
}
if (isset($dbi)) {
$this->addJSON('menu', $this->getHeader()->getMenu()->getDisplay());
}
$this->addJSON('scripts', $this->getHeader()->getScripts()->getFiles());
$this->addJSON('selflink', $this->getFooter()->getSelfUrl());
$this->addJSON('displayMessage', $this->getHeader()->getMessage());
$debug = $this->footer->getDebugMessage();
if (empty($_REQUEST['no_debug']) && strlen($debug) > 0) {
$this->addJSON('debug', $debug);
}
$errors = $this->footer->getErrorMessages();
if (strlen($errors) > 0) {
$this->addJSON('errors', $errors);
}
$promptPhpErrors = $GLOBALS['errorHandler']->hasErrorsForPrompt();
$this->addJSON('promptPhpErrors', $promptPhpErrors);
if (empty($GLOBALS['error_message'])) {
// set current db, table and sql query in the querywindow
// (this is for the bottom console)
$query = '';
$maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
if (isset($GLOBALS['sql_query']) && mb_strlen($GLOBALS['sql_query']) < $maxChars) {
$query = $GLOBALS['sql_query'];
}
$this->addJSON(
'reloadQuerywindow',
[
'db' => isset($GLOBALS['db']) && is_scalar($GLOBALS['db'])
? (string) $GLOBALS['db'] : '',
'table' => isset($GLOBALS['table']) && is_scalar($GLOBALS['table'])
? (string) $GLOBALS['table'] : '',
'sql_query' => $query,
]
);
if (! empty($GLOBALS['focus_querywindow'])) {
$this->addJSON('_focusQuerywindow', $query);
}
if (! empty($GLOBALS['reload'])) {
$this->addJSON('reloadNavigation', 1);
}
$this->addJSON('params', $this->getHeader()->getJsParams());
}
}
// Set the Content-Type header to JSON so that jQuery parses the
// response correctly.
Core::headerJSON();
$result = json_encode($this->JSON);
if ($result === false) {
return (string) json_encode([
'success' => false,
'error' => 'JSON encoding failed: ' . json_last_error_msg(),
]);
}
return $result;
}
/**
* Sends an HTML response to the browser
*/
public function response(): void
{
$buffer = OutputBuffering::getInstance();
if (empty($this->HTML)) {
$this->HTML = $buffer->getContents();
}
if ($this->isAjax()) {
echo $this->ajaxResponse();
} else {
echo $this->getDisplay();
}
$buffer->flush();
exit;
}
/**
* Wrapper around PHP's header() function.
*
* @param string $text header string
*/
public function header($text): void
{
// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
\header($text);
}
/**
* Wrapper around PHP's headers_sent() function.
*/
public function headersSent(): bool
{
return headers_sent();
}
/**
* Wrapper around PHP's http_response_code() function.
*
* @param int $response_code will set the response code.
*/
public function httpResponseCode($response_code): void
{
http_response_code($response_code);
}
/**
* Sets http response code.
*
* @param int $responseCode will set the response code.
*/
public function setHttpResponseCode(int $responseCode): void
{
$this->httpResponseCode($responseCode);
$header = 'status: ' . $responseCode . ' ';
if (isset(static::$httpStatusMessages[$responseCode])) {
$header .= static::$httpStatusMessages[$responseCode];
} else {
$header .= 'Web server is down';
}
if (PHP_SAPI === 'cgi-fcgi') {
return;
}
$this->header($header);
}
/**
* Generate header for 303
*
* @param string $location will set location to redirect.
*/
public function generateHeader303($location): void
{
$this->setHttpResponseCode(303);
$this->header('Location: ' . $location);
if (! defined('TESTSUITE')) {
exit;
}
}
/**
* Configures response for the login page
*
* @return bool Whether caller should exit
*/
public function loginPage(): bool
{
/* Handle AJAX redirection */
if ($this->isAjax()) {
$this->setRequestStatus(false);
// redirect_flag redirects to the login page
$this->addJSON('redirect_flag', '1');
return true;
}
$this->getFooter()->setMinimal();
$header = $this->getHeader();
$header->setBodyId('loginform');
$header->setTitle('phpMyAdmin');
$header->disableMenuAndConsole();
$header->disableWarnings();
return false;
}
}
こちらは修正完了です。以下はどうすればいいのですか?
/usr/share/phpmyadmin/libraries/classes/Header.php
<?php
/**
* Used to render the header of PMA's pages
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use PhpMyAdmin\ConfigStorage\Relation;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Navigation\Navigation;
use function array_merge;
use function defined;
use function gmdate;
use function header;
use function htmlspecialchars;
use function implode;
use function ini_get;
use function is_bool;
use function sprintf;
use function strlen;
use function strtolower;
use function urlencode;
/**
* Class used to output the HTTP and HTML headers
*/
class Header
{
/**
* Scripts instance
*
* @var Scripts
*/
private $scripts;
/**
* PhpMyAdmin\Console instance
*
* @var Console
*/
private $console;
/**
* Menu instance
*
* @var Menu
*/
private $menu;
/**
* The page title
*
* @var string
*/
private $title;
/**
* The value for the id attribute for the body tag
*
* @var string
*/
private $bodyId;
/**
* Whether to show the top menu
*
* @var bool
*/
private $menuEnabled;
/**
* Whether to show the warnings
*
* @var bool
*/
private $warningsEnabled;
/**
* Whether we are servicing an ajax request.
*
* @var bool
*/
private $isAjax;
/**
* Whether to display anything
*
* @var bool
*/
private $isEnabled;
/**
* Whether the HTTP headers (and possibly some HTML)
* have already been sent to the browser
*
* @var bool
*/
private $headerIsSent;
/** @var UserPreferences */
private $userPreferences;
/** @var Template */
private $template;
/**
* Creates a new class instance
*/
public function __construct()
{
global $db, $table, $dbi;
$this->template = new Template();
$this->isEnabled = true;
$this->isAjax = false;
$this->bodyId = '';
$this->title = '';
$this->console = new Console();
$this->menu = new Menu($dbi, $db ?? '', $table ?? '');
$this->menuEnabled = true;
$this->warningsEnabled = true;
$this->scripts = new Scripts();
$this->addDefaultScripts();
$this->headerIsSent = false;
$this->userPreferences = new UserPreferences();
}
/**
* Loads common scripts
*/
private function addDefaultScripts(): void
{
// Localised strings
$this->scripts->addFile('vendor/jquery/jquery.min.js');
$this->scripts->addFile('vendor/jquery/jquery-migrate.min.js');
$this->scripts->addFile('vendor/sprintf.js');
$this->scripts->addFile('ajax.js');
$this->scripts->addFile('keyhandler.js');
$this->scripts->addFile('vendor/jquery/jquery-ui.min.js');
$this->scripts->addFile('name-conflict-fixes.js');
$this->scripts->addFile('vendor/bootstrap/bootstrap.bundle.min.js');
$this->scripts->addFile('vendor/js.cookie.js');
$this->scripts->addFile('vendor/jquery/jquery.validate.min.js');
$this->scripts->addFile('vendor/jquery/jquery-ui-timepicker-addon.js');
$this->scripts->addFile('vendor/jquery/jquery.debounce-1.0.6.js');
$this->scripts->addFile('menu_resizer.js');
// Cross-framing protection
// At this point browser settings are not merged
// this is good that we only use file configuration for this protection
if ($GLOBALS['cfg']['AllowThirdPartyFraming'] === false) {
$this->scripts->addFile('cross_framing_protection.js');
}
// Here would not be a good place to add CodeMirror because
// the user preferences have not been merged at this point
$this->scripts->addFile('messages.php', ['l' => $GLOBALS['lang']]);
$this->scripts->addFile('config.js');
$this->scripts->addFile('doclinks.js');
$this->scripts->addFile('functions.js');
$this->scripts->addFile('navigation.js');
$this->scripts->addFile('indexes.js');
$this->scripts->addFile('common.js');
$this->scripts->addFile('page_settings.js');
$this->scripts->addCode($this->getJsParamsCode());
}
/**
* Returns, as an array, a list of parameters
* used on the client side
*
* @return array
*/
public function getJsParams(): array
{
global $db, $table, $dbi;
$pftext = $_SESSION['tmpval']['pftext'] ?? '';
$params = [
// Do not add any separator, JS code will decide
'common_query' => Url::getCommonRaw([], ''),
'opendb_url' => Util::getScriptNameForOption($GLOBALS['cfg']['DefaultTabDatabase'], 'database'),
'lang' => $GLOBALS['lang'],
'server' => $GLOBALS['server'],
'table' => $table ?? '',
'db' => $db ?? '',
'token' => $_SESSION[' PMA_token '],
'text_dir' => $GLOBALS['text_dir'],
'LimitChars' => $GLOBALS['cfg']['LimitChars'],
'pftext' => $pftext,
'confirm' => $GLOBALS['cfg']['Confirm'],
'LoginCookieValidity' => $GLOBALS['cfg']['LoginCookieValidity'],
'session_gc_maxlifetime' => (int) ini_get('session.gc_maxlifetime'),
'logged_in' => isset($dbi) ? $dbi->isConnected() : false,
'is_https' => $GLOBALS['config']->isHttps(),
'rootPath' => $GLOBALS['config']->getRootPath(),
'arg_separator' => Url::getArgSeparator(),
'version' => Version::VERSION,
];
if (isset($GLOBALS['cfg']['Server'], $GLOBALS['cfg']['Server']['auth_type'])) {
$params['auth_type'] = $GLOBALS['cfg']['Server']['auth_type'];
if (isset($GLOBALS['cfg']['Server']['user'])) {
$params['user'] = $GLOBALS['cfg']['Server']['user'];
}
}
return $params;
}
/**
* Returns, as a string, a list of parameters
* used on the client side
*/
public function getJsParamsCode(): string
{
$params = $this->getJsParams();
foreach ($params as $key => $value) {
if (is_bool($value)) {
$params[$key] = $key . ':' . ($value ? 'true' : 'false') . '';
} else {
$params[$key] = $key . ':"' . Sanitize::escapeJsString($value) . '"';
}
}
return 'CommonParams.setAll({' . implode(',', $params) . '});';
}
/**
* Disables the rendering of the header
*/
public function disable(): void
{
$this->isEnabled = false;
}
/**
* 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
*
* @return Scripts object
*/
public function getScripts(): Scripts
{
return $this->scripts;
}
/**
* Returns the Menu object
*
* @return Menu object
*/
public function getMenu(): Menu
{
return $this->menu;
}
/**
* Setter for the ID attribute in the BODY tag
*
* @param string $id Value for the ID attribute
*/
public function setBodyId(string $id): void
{
$this->bodyId = htmlspecialchars($id);
}
/**
* Setter for the title of the page
*
* @param string $title New title
*/
public function setTitle(string $title): void
{
$this->title = htmlspecialchars($title);
}
/**
* Disables the display of the top menu
*/
public function disableMenuAndConsole(): void
{
$this->menuEnabled = false;
$this->console->disable();
}
/**
* Disables the display of the top menu
*/
public function disableWarnings(): void
{
$this->warningsEnabled = false;
}
/**
* Generates the header
*
* @return string The header
*/
public function getDisplay(): string
{
global $db, $table, $theme, $dbi;
if ($this->headerIsSent || ! $this->isEnabled) {
return '';
}
$recentTable = '';
if (empty($_REQUEST['recent_table'])) {
$recentTable = $this->addRecentTable($db, $table);
}
if ($this->isAjax) {
return $recentTable;
}
$this->sendHttpHeaders();
$baseDir = defined('PMA_PATH_TO_BASEDIR') ? PMA_PATH_TO_BASEDIR : '';
$themePath = $theme instanceof Theme ? $theme->getPath() : '';
$version = self::getVersionParameter();
// The user preferences have been merged at this point
// so we can conditionally add CodeMirror, other scripts and settings
if ($GLOBALS['cfg']['CodemirrorEnable']) {
$this->scripts->addFile('vendor/codemirror/lib/codemirror.js');
$this->scripts->addFile('vendor/codemirror/mode/sql/sql.js');
$this->scripts->addFile('vendor/codemirror/addon/runmode/runmode.js');
$this->scripts->addFile('vendor/codemirror/addon/hint/show-hint.js');
$this->scripts->addFile('vendor/codemirror/addon/hint/sql-hint.js');
if ($GLOBALS['cfg']['LintEnable']) {
$this->scripts->addFile('vendor/codemirror/addon/lint/lint.js');
$this->scripts->addFile('codemirror/addon/lint/sql-lint.js');
}
}
if ($GLOBALS['cfg']['SendErrorReports'] !== 'never') {
$this->scripts->addFile('vendor/tracekit.js');
$this->scripts->addFile('error_report.js');
}
if ($GLOBALS['cfg']['enable_drag_drop_import'] === true) {
$this->scripts->addFile('drag_drop_import.js');
}
if (! $GLOBALS['config']->get('DisableShortcutKeys')) {
$this->scripts->addFile('shortcuts_handler.js');
}
$this->scripts->addCode($this->getVariablesForJavaScript());
$this->scripts->addCode('ConsoleEnterExecutes=' . ($GLOBALS['cfg']['ConsoleEnterExecutes'] ? 'true' : 'false'));
$this->scripts->addFiles($this->console->getScripts());
// if database storage for user preferences is transient,
// offer to load exported settings from localStorage
// (detection will be done in JavaScript)
$userprefsOfferImport = false;
if (
$GLOBALS['config']->get('user_preferences') === 'session'
&& ! isset($_SESSION['userprefs_autoload'])
) {
$userprefsOfferImport = true;
}
if ($userprefsOfferImport) {
$this->scripts->addFile('config.js');
}
if ($this->menuEnabled && $GLOBALS['server'] > 0) {
$nav = new Navigation(
$this->template,
new Relation($dbi),
$dbi
);
$navigation = $nav->getDisplay();
}
$customHeader = Config::renderHeader();
// offer to load user preferences from localStorage
if ($userprefsOfferImport) {
$loadUserPreferences = $this->userPreferences->autoloadGetHeader();
}
if ($this->menuEnabled && $GLOBALS['server'] > 0) {
$menu = $this->menu->getDisplay();
}
$console = $this->console->getDisplay();
$messages = $this->getMessage();
$isLoggedIn = isset($GLOBALS['dbi']) && $GLOBALS['dbi']->isConnected();
return $this->template->render('header', [
'lang' => $GLOBALS['lang'],
'allow_third_party_framing' => $GLOBALS['cfg']['AllowThirdPartyFraming'],
'base_dir' => $baseDir,
'theme_path' => $themePath,
'version' => $version,
'text_dir' => $GLOBALS['text_dir'],
'server' => $GLOBALS['server'] ?? null,
'title' => $this->getPageTitle(),
'scripts' => $this->scripts->getDisplay(),
'body_id' => $this->bodyId,
'navigation' => $navigation ?? '',
'custom_header' => $customHeader,
'load_user_preferences' => $loadUserPreferences ?? '',
'show_hint' => $GLOBALS['cfg']['ShowHint'],
'is_warnings_enabled' => $this->warningsEnabled,
'is_menu_enabled' => $this->menuEnabled,
'is_logged_in' => $isLoggedIn,
'menu' => $menu ?? '',
'console' => $console,
'messages' => $messages,
'recent_table' => $recentTable,
]);
}
/**
* Returns the message to be displayed at the top of
* the page, including the executed SQL query, if any.
*/
public function getMessage(): string
{
$retval = '';
$message = '';
if (! empty($GLOBALS['message'])) {
$message = $GLOBALS['message'];
unset($GLOBALS['message']);
} elseif (! empty($_REQUEST['message'])) {
$message = $_REQUEST['message'];
}
if (! empty($message)) {
if (isset($GLOBALS['buffer_message'])) {
$bufferMessage = $GLOBALS['buffer_message'];
}
$retval .= Generator::getMessage($message);
if (isset($bufferMessage)) {
$GLOBALS['buffer_message'] = $bufferMessage;
}
}
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));
}
$this->headerIsSent = true;
}
/**
* @return array<string, string>
*/
private function getHttpHeaders(): array
{
$headers = [];
/* Prevent against ClickJacking by disabling framing */
if (strtolower((string) $GLOBALS['cfg']['AllowThirdPartyFraming']) === 'sameorigin') {
$headers['X-Frame-Options'] = 'SAMEORIGIN';
} elseif ($GLOBALS['cfg']['AllowThirdPartyFraming'] !== true) {
$headers['X-Frame-Options'] = 'DENY';
}
$headers['Referrer-Policy'] = 'no-referrer';
$headers = array_merge($headers, $this->getCspHeaders());
/**
* Re-enable possible disabled XSS filters.
*
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
$headers['X-XSS-Protection'] = '1; mode=block';
/**
* "nosniff", prevents Internet Explorer and Google Chrome from MIME-sniffing
* a response away from the declared content-type.
*
* @see https://www.owasp.org/index.php/List_of_useful_HTTP_headers
*/
$headers['X-Content-Type-Options'] = 'nosniff';
/**
* Adobe cross-domain-policies.
*
* @see https://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html
*/
$headers['X-Permitted-Cross-Domain-Policies'] = 'none';
/**
* Robots meta tag.
*
* @see https://developers.google.com/webmasters/control-crawl-index/docs/robots_meta_tag
*/
$headers['X-Robots-Tag'] = 'noindex, nofollow';
$headers = array_merge($headers, Core::getNoCacheHeaders());
if (! defined('IS_TRANSFORMATION_WRAPPER')) {
// Define the charset to be used
$headers['Content-Type'] = 'text/html; charset=utf-8';
}
return $headers;
}
/**
* If the page is missing the title, this function
* will set it to something reasonable
*/
public function getPageTitle(): string
{
if (strlen($this->title) == 0) {
if ($GLOBALS['server'] > 0) {
if (strlen($GLOBALS['table'])) {
$tempTitle = $GLOBALS['cfg']['TitleTable'];
} elseif (strlen($GLOBALS['db'])) {
$tempTitle = $GLOBALS['cfg']['TitleDatabase'];
} elseif (strlen($GLOBALS['cfg']['Server']['host'])) {
$tempTitle = $GLOBALS['cfg']['TitleServer'];
} else {
$tempTitle = $GLOBALS['cfg']['TitleDefault'];
}
$this->title = htmlspecialchars(
Util::expandUserString($tempTitle)
);
} else {
$this->title = 'phpMyAdmin';
}
}
return $this->title;
}
/**
* Get all the CSP allow policy headers
*
* @return array<string, string>
*/
private function getCspHeaders(): array
{
global $cfg;
$mapTileUrls = ' *.tile.openstreetmap.org';
$captchaUrl = '';
$cspAllow = $cfg['CSPAllow'];
if (
! empty($cfg['CaptchaLoginPrivateKey'])
&& ! empty($cfg['CaptchaLoginPublicKey'])
&& ! empty($cfg['CaptchaApi'])
&& ! empty($cfg['CaptchaRequestParam'])
&& ! empty($cfg['CaptchaResponseParam'])
) {
$captchaUrl = ' ' . $cfg['CaptchaCsp'] . ' ';
}
$headers = [];
$headers['Content-Security-Policy'] = sprintf(
'default-src \'self\' %s%s;script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' %s%s;'
. 'style-src \'self\' \'unsafe-inline\' %s%s;img-src \'self\' data: %s%s%s;object-src \'none\';',
$captchaUrl,
$cspAllow,
$captchaUrl,
$cspAllow,
$captchaUrl,
$cspAllow,
$cspAllow,
$mapTileUrls,
$captchaUrl
);
$headers['X-Content-Security-Policy'] = sprintf(
'default-src \'self\' %s%s;options inline-script eval-script;'
. 'referrer no-referrer;img-src \'self\' data: %s%s%s;object-src \'none\';',
$captchaUrl,
$cspAllow,
$cspAllow,
$mapTileUrls,
$captchaUrl
);
$headers['X-WebKit-CSP'] = sprintf(
'default-src \'self\' %s%s;script-src \'self\' %s%s \'unsafe-inline\' \'unsafe-eval\';'
. 'referrer no-referrer;style-src \'self\' \'unsafe-inline\' %s;'
. 'img-src \'self\' data: %s%s%s;object-src \'none\';',
$captchaUrl,
$cspAllow,
$captchaUrl,
$cspAllow,
$captchaUrl,
$cspAllow,
$mapTileUrls,
$captchaUrl
);
return $headers;
}
/**
* Add recently used table and reload the navigation.
*
* @param string $db Database name where the table is located.
* @param string $table The table name
*/
private function addRecentTable(string $db, string $table): string
{
$retval = '';
if ($this->menuEnabled && strlen($table) > 0 && $GLOBALS['cfg']['NumRecentTables'] > 0) {
$tmpResult = RecentFavoriteTable::getInstance('recent')->add($db, $table);
if ($tmpResult === true) {
$retval = RecentFavoriteTable::getHtmlUpdateRecentTables();
} else {
$error = $tmpResult;
$retval = $error->getDisplay();
}
}
return $retval;
}
/**
* Returns the phpMyAdmin version to be appended to the url to avoid caching
* between versions
*
* @return string urlencoded pma version as a parameter
*/
public static function getVersionParameter(): string
{
return 'v=' . urlencode(Version::VERSION);
}
private function getVariablesForJavaScript(): string
{
global $cfg;
$maxInputVars = ini_get('max_input_vars');
$maxInputVarsValue = $maxInputVars === false || $maxInputVars === '' ? 'false' : (int) $maxInputVars;
return $this->template->render('javascript/variables', [
'first_day_of_calendar' => $cfg['FirstDayOfCalendar'] ?? 0,
'max_input_vars' => $maxInputVarsValue,
]);
}
}
/usr/share/phpmyadmin/libraries/classes/Footer.php
<?php
/**
* Used to render the footer of PMA's pages
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use PhpMyAdmin\ConfigStorage\Relation;
use Traversable;
use function basename;
use function file_exists;
use function in_array;
use function is_array;
use function is_object;
use function is_scalar;
use function json_encode;
use function json_last_error;
use function strlen;
/**
* Class used to output the footer
*/
class Footer
{
/**
* Scripts instance
*
* @var Scripts
*/
private $scripts;
/**
* Whether we are servicing an ajax request.
*
* @var bool
*/
private $isAjax;
/**
* Whether to only close the BODY and HTML tags
* or also include scripts, errors and links
*
* @var bool
*/
private $isMinimal;
/**
* Whether to display anything
*
* @var bool
*/
private $isEnabled;
/** @var Relation */
private $relation;
/** @var Template */
private $template;
/**
* Creates a new class instance
*/
public function __construct()
{
global $dbi;
$this->template = new Template();
$this->isEnabled = true;
$this->scripts = new Scripts();
$this->isMinimal = false;
$this->relation = new Relation($dbi);
}
/**
* @return array<string, string>
* @psalm-return array{revision: string, revisionUrl: string, branch: string, branchUrl: string}|[]
*/
private function getGitRevisionInfo(): array
{
$info = [];
if (@file_exists(ROOT_PATH . 'revision-info.php')) {
/** @psalm-suppress MissingFile,UnresolvableInclude */
$info = include ROOT_PATH . 'revision-info.php';
}
return is_array($info) ? $info : [];
}
/**
* Remove recursions and iterator objects from an object
*
* @param mixed $object Object to clean
* @param array $stack Stack used to keep track of recursion, need not be passed for the first time
*
* @return mixed Reference passed object
*/
private static function removeRecursion(&$object, array $stack = [])
{
if ((is_object($object) || is_array($object)) && $object) {
if ($object instanceof Traversable) {
$object = '***ITERATOR***';
} elseif (! in_array($object, $stack, true)) {
$stack[] = $object;
// @phpstan-ignore-next-line
foreach ($object as &$subObject) {
self::removeRecursion($subObject, $stack);
}
} else {
$object = '***RECURSION***';
}
}
return $object;
}
/**
* Renders the debug messages
*/
public function getDebugMessage(): string
{
$retval = '\'null\'';
if ($GLOBALS['cfg']['DBG']['sql'] && empty($_REQUEST['no_debug']) && ! empty($_SESSION['debug'])) {
// Remove recursions and iterators from $_SESSION['debug']
self::removeRecursion($_SESSION['debug']);
$retval = (string) json_encode($_SESSION['debug']);
$_SESSION['debug'] = [];
return json_last_error() ? '\'false\'' : $retval;
}
$_SESSION['debug'] = [];
return $retval;
}
/**
* Returns the url of the current page
*/
public function getSelfUrl(): string
{
global $route, $db, $table, $server;
$params = [];
if (isset($route)) {
$params['route'] = $route;
}
if (isset($db) && strlen($db) > 0) {
$params['db'] = $db;
}
if (isset($table) && strlen($table) > 0) {
$params['table'] = $table;
}
$params['server'] = $server;
// needed for server privileges tabs
if (isset($_GET['viewing_mode']) && in_array($_GET['viewing_mode'], ['server', 'db', 'table'])) {
$params['viewing_mode'] = $_GET['viewing_mode'];
}
/**
* @todo coming from /server/privileges, here $db is not set,
* add the following condition below when that is fixed
* && $_GET['checkprivsdb'] == $db
*/
if (isset($_GET['checkprivsdb'])) {
$params['checkprivsdb'] = $_GET['checkprivsdb'];
}
/**
* @todo coming from /server/privileges, here $table is not set,
* add the following condition below when that is fixed
* && $_REQUEST['checkprivstable'] == $table
*/
if (isset($_GET['checkprivstable'])) {
$params['checkprivstable'] = $_GET['checkprivstable'];
}
if (isset($_REQUEST['single_table']) && in_array($_REQUEST['single_table'], [true, false])) {
$params['single_table'] = $_REQUEST['single_table'];
}
return basename(Core::getenv('SCRIPT_NAME')) . Url::getCommonRaw($params);
}
/**
* Renders the link to open a new page
*/
public function getErrorMessages(): string
{
$retval = '';
if ($GLOBALS['errorHandler']->hasDisplayErrors()) {
$retval .= $GLOBALS['errorHandler']->getDispErrors();
}
/**
* Report php errors
*/
$GLOBALS['errorHandler']->reportErrors();
return $retval;
}
/**
* Saves query in history
*/
private function setHistory(): void
{
global $dbi;
if (
(
isset($_REQUEST['no_history'])
&& is_scalar($_REQUEST['no_history'])
&& strlen((string) $_REQUEST['no_history']) > 0
)
|| ! empty($GLOBALS['error_message'])
|| empty($GLOBALS['sql_query'])
|| ! isset($dbi)
|| ! $dbi->isConnected()
) {
return;
}
$this->relation->setHistory(
isset($GLOBALS['db']) && is_scalar($GLOBALS['db']) ? (string) $GLOBALS['db'] : '',
isset($GLOBALS['table']) && is_scalar($GLOBALS['table']) ? (string) $GLOBALS['table'] : '',
$GLOBALS['cfg']['Server']['user'],
$GLOBALS['sql_query']
);
}
/**
* Disables the rendering of the footer
*/
public function disable(): void
{
$this->isEnabled = false;
}
/**
* 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
*/
public function setMinimal(): void
{
$this->isMinimal = true;
}
/**
* Returns the Scripts object
*
* @return Scripts object
*/
public function getScripts(): Scripts
{
return $this->scripts;
}
/**
* Renders the footer
*/
public function getDisplay(): string
{
$this->setHistory();
if ($this->isEnabled) {
if (! $this->isAjax && ! $this->isMinimal) {
if (Core::getenv('SCRIPT_NAME')) {
$url = $this->getSelfUrl();
}
$this->scripts->addCode('var debugSQLInfo = ' . $this->getDebugMessage() . ';');
$errorMessages = $this->getErrorMessages();
$scripts = $this->scripts->getDisplay();
if ($GLOBALS['cfg']['DBG']['demo']) {
$gitRevisionInfo = $this->getGitRevisionInfo();
}
$footer = Config::renderFooter();
}
return $this->template->render('footer', [
'is_ajax' => $this->isAjax,
'is_minimal' => $this->isMinimal,
'self_url' => $url ?? null,
'error_messages' => $errorMessages ?? '',
'scripts' => $scripts ?? '',
'is_demo' => $GLOBALS['cfg']['DBG']['demo'],
'git_revision_info' => $gitRevisionInfo ?? [],
'footer' => $footer ?? '',
]);
}
return '';
}
}
回答
`ResponseRenderer`, `Header`, `Footer` クラスの内容を確認しました。提供されたコードから、クラスのオブジェクトが正しい形でインスタンス化されていることがわかります。`getDisplay()` メソッド内で `header` や `footer` オブジェクトの `getDisplay()` メソッドが呼び出されている部分で問題が発生している可能性がありますが、明らかな問題は見つかりませんでした。
続いて、`phpMyAdmin\Template` クラスが正しく動作しているか、または他に依存関係が不足しているかを確認する必要があります。
### 手順1: `Template` クラスの確認
`Template` クラスの内容を確認し、問題がないか確認します。このクラスが適切に読み込まれ、初期化されていることを確認します。
以下のコマンドで `Template` クラスを含むファイルを確認します:
bash
sudo nano /usr/share/phpmyadmin/libraries/classes/Template.php
### 手順2: `getDisplay` メソッドのデバッグ
次に `getDisplay` メソッドをデバッグします。`header` や `footer` オブジェクトが存在し、正しい値を返すかを確認するために、デバッグメッセージを追加します。
php
private function getDisplay(): string
{
$retval = '';
if ($this->header !== null) {
$retval .= "Header exists: " . get_class($this->header) . "\n";
$retval .= $this->header->getDisplay();
} else {
$retval .= "Header object is null.\n";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= "Footer exists: " . get_class($this->footer) . "\n";
$retval .= $this->footer->getDisplay();
} else {
$retval .= "Footer object is null.\n";
}
return $retval;
}
### 手順3: Apacheの再起動とエラーログの確認
変更を適用するためにApacheを再起動し、エラーログを再確認します:
bash
sudo composer dump-autoload
sudo systemctl restart apache2
sudo tail -n 50 /var/log/apache2/error.log
### まとめ
以下の手順に従って、問題をデバッグおよび修正してください:
1. **`Template` クラスの内容を確認**:
bash
sudo nano /usr/share/phpmyadmin/libraries/classes/Template.php
2. **`getDisplay` メソッドのデバッグ**:
- `ResponseRenderer.php` の該当箇所を以下のように修正:
php
private function getDisplay(): string
{
$retval = '';
if ($this->header !== null) {
$retval .= "Header exists: " . get_class($this->header) . "\n";
$retval .= $this->header->getDisplay();
} else {
$retval .= "Header object is null.\n";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= "Footer exists: " . get_class($this->footer) . "\n";
$retval .= $this->footer->getDisplay();
} else {
$retval .= "Footer object is null.\n";
}
return $retval;
}
3. **Apacheの再起動とエラーログの確認**:
bash
sudo composer dump-autoload
sudo systemctl restart apache2
sudo tail -n 50 /var/log/apache2/error.log
これで、問題の根本的な原因が特定され、phpMyAdminが正常に動作することを期待しています。依然としてエラーが解決しない場合は、新たなエラーメッセージを共有してください。さらなるサポートを提供いたします。
質問
/usr/share/phpmyadmin/libraries/classes/Template.php
<?php
declare(strict_types=1);
namespace PhpMyAdmin;
use PhpMyAdmin\Twig\AssetExtension;
use PhpMyAdmin\Twig\CoreExtension;
use PhpMyAdmin\Twig\Extensions\Node\TransNode;
use PhpMyAdmin\Twig\FlashMessagesExtension;
use PhpMyAdmin\Twig\I18nExtension;
use PhpMyAdmin\Twig\MessageExtension;
use PhpMyAdmin\Twig\RelationExtension;
use PhpMyAdmin\Twig\SanitizeExtension;
use PhpMyAdmin\Twig\TableExtension;
use PhpMyAdmin\Twig\TrackerExtension;
use PhpMyAdmin\Twig\TransformationsExtension;
use PhpMyAdmin\Twig\UrlExtension;
use PhpMyAdmin\Twig\UtilExtension;
use RuntimeException;
use Throwable;
use Twig\Environment;
use Twig\Error\LoaderError;
use Twig\Error\RuntimeError;
use Twig\Error\SyntaxError;
use Twig\Extension\DebugExtension;
use Twig\Loader\FilesystemLoader;
use Twig\RuntimeLoader\ContainerRuntimeLoader;
use Twig\TemplateWrapper;
use function __;
use function is_array;
use function sprintf;
use function trigger_error;
use const E_USER_WARNING;
/**
* Handle front end templating
*/
class Template
{
/**
* Twig environment
*
* @var Environment
*/
protected static $twig;
public const TEMPLATES_FOLDER = ROOT_PATH . 'templates';
public function __construct()
{
if (static::$twig !== null) {
return;
}
/** @var Config|null $config */
$config = $GLOBALS['config'];
$cacheDir = $config !== null ? $config->getTempDir('twig') : null;
static::$twig = self::getTwigEnvironment($cacheDir);
}
public static function getTwigEnvironment(?string $cacheDir): Environment
{
global $cfg, $containerBuilder;
/* Twig expects false when cache is not configured */
if ($cacheDir === null) {
$cacheDir = false;
}
$loader = new FilesystemLoader(self::TEMPLATES_FOLDER);
$twig = new Environment($loader, [
'auto_reload' => true,
'cache' => $cacheDir,
]);
$twig->addRuntimeLoader(new ContainerRuntimeLoader($containerBuilder));
if (is_array($cfg) && ($cfg['environment'] ?? '') === 'development') {
$twig->enableDebug();
$twig->addExtension(new DebugExtension());
// This will enable debug for the extension to print lines
// It is used in po file lines re-mapping
TransNode::$enableAddDebugInfo = true;
}
if ($cfg['environment'] === 'production') {
$twig->disableDebug();
TransNode::$enableAddDebugInfo = false;
}
$twig->addExtension(new AssetExtension());
$twig->addExtension(new CoreExtension());
$twig->addExtension(new FlashMessagesExtension());
$twig->addExtension(new I18nExtension());
$twig->addExtension(new MessageExtension());
$twig->addExtension(new RelationExtension());
$twig->addExtension(new SanitizeExtension());
$twig->addExtension(new TableExtension());
$twig->addExtension(new TrackerExtension());
$twig->addExtension(new TransformationsExtension());
$twig->addExtension(new UrlExtension());
$twig->addExtension(new UtilExtension());
return $twig;
}
/**
* Loads a template.
*
* @param string $templateName Template path name
*
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
private function load(string $templateName): TemplateWrapper
{
try {
$template = static::$twig->load($templateName . '.twig');
} catch (RuntimeException $e) {
/* Retry with disabled cache */
static::$twig->setCache(false);
$template = static::$twig->load($templateName . '.twig');
/*
* The trigger error is intentionally after second load
* to avoid triggering error when disabling cache does not
* solve it.
*/
trigger_error(
sprintf(
__('Error while working with template cache: %s'),
$e->getMessage()
),
E_USER_WARNING
);
}
return $template;
}
/**
* @param string $template Template path name
* @param array $data Associative array of template variables
*
* @throws Throwable
* @throws LoaderError
* @throws RuntimeError
* @throws SyntaxError
*/
public function render(string $template, array $data = []): string
{
return $this->load($template)->render($data);
}
}
これはどこを見るのですか?
/usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php
<?php
/**
* Manages the rendering of pages in PMA
*/
declare(strict_types=1);
namespace PhpMyAdmin;
use function defined;
use function headers_sent;
use function http_response_code;
use function is_array;
use function is_scalar;
use function json_encode;
use function json_last_error_msg;
use function mb_strlen;
use function register_shutdown_function;
use function strlen;
use const PHP_SAPI;
/**
* Singleton class used to manage the rendering of pages in PMA
*/
class ResponseRenderer
{
/**
* Response instance
*
* @static
* @var ResponseRenderer
*/
private static $instance;
/**
* Header instance
*
* @var Header
*/
protected $header;
/**
* HTML data to be used in the response
*
* @var string
*/
private $HTML;
/**
* An array of JSON key-value pairs
* to be sent back for ajax requests
*
* @var array
*/
private $JSON;
/**
* PhpMyAdmin\Footer instance
*
* @var Footer
*/
protected $footer;
/**
* Whether we are servicing an ajax request.
*
* @var bool
*/
protected $isAjax = false;
/**
* Whether response object is disabled
*
* @var bool
*/
private $isDisabled;
/**
* Whether there were any errors during the processing of the request
* Only used for ajax responses
*
* @var bool
*/
protected $isSuccess;
/**
* @see http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml
*
* @var array<int, string>
*/
protected static $httpStatusMessages = [
// Informational
100 => 'Continue',
101 => 'Switching Protocols',
102 => 'Processing',
103 => 'Early Hints',
// Success
200 => 'OK',
201 => 'Created',
202 => 'Accepted',
203 => 'Non-Authoritative Information',
204 => 'No Content',
205 => 'Reset Content',
206 => 'Partial Content',
207 => 'Multi-Status',
208 => 'Already Reported',
226 => 'IM Used',
// Redirection
300 => 'Multiple Choices',
301 => 'Moved Permanently',
302 => 'Found',
303 => 'See Other',
304 => 'Not Modified',
305 => 'Use Proxy',
307 => 'Temporary Redirect',
308 => 'Permanent Redirect',
// Client Error
400 => 'Bad Request',
401 => 'Unauthorized',
402 => 'Payment Required',
403 => 'Forbidden',
404 => 'Not Found',
405 => 'Method Not Allowed',
406 => 'Not Acceptable',
407 => 'Proxy Authentication Required',
408 => 'Request Timeout',
409 => 'Conflict',
410 => 'Gone',
411 => 'Length Required',
412 => 'Precondition Failed',
413 => 'Payload Too Large',
414 => 'URI Too Long',
415 => 'Unsupported Media Type',
416 => 'Range Not Satisfiable',
417 => 'Expectation Failed',
421 => 'Misdirected Request',
422 => 'Unprocessable Entity',
423 => 'Locked',
424 => 'Failed Dependency',
425 => 'Too Early',
426 => 'Upgrade Required',
427 => 'Unassigned',
428 => 'Precondition Required',
429 => 'Too Many Requests',
430 => 'Unassigned',
431 => 'Request Header Fields Too Large',
451 => 'Unavailable For Legal Reasons',
// Server Error
500 => 'Internal Server Error',
501 => 'Not Implemented',
502 => 'Bad Gateway',
503 => 'Service Unavailable',
504 => 'Gateway Timeout',
505 => 'HTTP Version Not Supported',
506 => 'Variant Also Negotiates',
507 => 'Insufficient Storage',
508 => 'Loop Detected',
509 => 'Unassigned',
510 => 'Not Extended',
511 => 'Network Authentication Required',
];
/**
* Creates a new class instance
*/
private function __construct()
{
if (! defined('TESTSUITE')) {
$buffer = OutputBuffering::getInstance();
$buffer->start();
register_shutdown_function([$this, 'response']);
}
$this->header = new Header();
$this->HTML = '';
$this->JSON = [];
$this->footer = new Footer();
$this->isSuccess = true;
$this->isDisabled = false;
$this->setAjax(! empty($_REQUEST['ajax_request']));
}
/**
* 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->header->setAjax($this->isAjax);
$this->footer->setAjax($this->isAjax);
}
/**
* Returns the singleton Response object
*
* @return ResponseRenderer object
*/
public static function getInstance()
{
if (empty(self::$instance)) {
self::$instance = new ResponseRenderer();
}
return self::$instance;
}
/**
* Set the status of an ajax response,
* whether it is a success or an error
*
* @param bool $state Whether the request was successfully processed
*/
public function setRequestStatus(bool $state): void
{
$this->isSuccess = ($state === true);
}
/**
* Returns true or false depending on whether
* we are servicing an ajax request
*/
public function isAjax(): bool
{
return $this->isAjax;
}
/**
* Disables the rendering of the header
* and the footer in responses
*/
public function disable(): void
{
$this->header->disable();
$this->footer->disable();
$this->isDisabled = true;
}
/**
* Returns a PhpMyAdmin\Header object
*
* @return Header
*/
public function getHeader()
{
return $this->header;
}
/**
* Returns a PhpMyAdmin\Footer object
*
* @return Footer
*/
public function getFooter()
{
return $this->footer;
}
/**
* Append HTML code to the current output buffer
*/
public function addHTML(string $content): void
{
$this->HTML .= $content;
}
/**
* Add JSON code to the response
*
* @param string|int|array $json Either a key (string) or an array or key-value pairs
* @param mixed|null $value Null, if passing an array in $json otherwise
* it's a string value to the key
*/
public function addJSON($json, $value = null): void
{
if (is_array($json)) {
foreach ($json as $key => $value) {
$this->addJSON($key, $value);
}
} elseif ($value instanceof Message) {
$this->JSON[$json] = $value->getDisplay();
} else {
$this->JSON[$json] = $value;
}
}
/**
* Renders the HTML response text
*/
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
$retval = '';
if ($this->header !== null) {
$retval .= "Header exists: " . get_class($this->header) . "\n";
$retval .= $this->header->getDisplay();
} else {
$retval .= "Header object is null.\n";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= "Footer exists: " . get_class($this->footer) . "\n";
$retval .= $this->footer->getDisplay();
} else {
$retval .= "Footer object is null.\n";
}
return $retval;
}
/**
* Sends a JSON response to the browser
*/
private function ajaxResponse(): string
{
global $dbi;
/* Avoid wrapping in case we're disabled */
if ($this->isDisabled) {
return $this->getDisplay();
}
if (! isset($this->JSON['message'])) {
$this->JSON['message'] = $this->getDisplay();
} elseif ($this->JSON['message'] instanceof Message) {
$this->JSON['message'] = $this->JSON['message']->getDisplay();
}
if ($this->isSuccess) {
$this->JSON['success'] = true;
} else {
$this->JSON['success'] = false;
$this->JSON['error'] = $this->JSON['message'];
unset($this->JSON['message']);
}
if ($this->isSuccess) {
if (! isset($this->JSON['title'])) {
$this->addJSON('title', '<title>' . $this->getHeader()->getPageTitle() . '</title>');
}
if (isset($dbi)) {
$this->addJSON('menu', $this->getHeader()->getMenu()->getDisplay());
}
$this->addJSON('scripts', $this->getHeader()->getScripts()->getFiles());
$this->addJSON('selflink', $this->getFooter()->getSelfUrl());
$this->addJSON('displayMessage', $this->getHeader()->getMessage());
$debug = $this->footer->getDebugMessage();
if (empty($_REQUEST['no_debug']) && strlen($debug) > 0) {
$this->addJSON('debug', $debug);
}
$errors = $this->footer->getErrorMessages();
if (strlen($errors) > 0) {
$this->addJSON('errors', $errors);
}
$promptPhpErrors = $GLOBALS['errorHandler']->hasErrorsForPrompt();
$this->addJSON('promptPhpErrors', $promptPhpErrors);
if (empty($GLOBALS['error_message'])) {
// set current db, table and sql query in the querywindow
// (this is for the bottom console)
$query = '';
$maxChars = $GLOBALS['cfg']['MaxCharactersInDisplayedSQL'];
if (isset($GLOBALS['sql_query']) && mb_strlen($GLOBALS['sql_query']) < $maxChars) {
$query = $GLOBALS['sql_query'];
}
$this->addJSON(
'reloadQuerywindow',
[
'db' => isset($GLOBALS['db']) && is_scalar($GLOBALS['db'])
? (string) $GLOBALS['db'] : '',
'table' => isset($GLOBALS['table']) && is_scalar($GLOBALS['table'])
? (string) $GLOBALS['table'] : '',
'sql_query' => $query,
]
);
if (! empty($GLOBALS['focus_querywindow'])) {
$this->addJSON('_focusQuerywindow', $query);
}
if (! empty($GLOBALS['reload'])) {
$this->addJSON('reloadNavigation', 1);
}
$this->addJSON('params', $this->getHeader()->getJsParams());
}
}
// Set the Content-Type header to JSON so that jQuery parses the
// response correctly.
Core::headerJSON();
$result = json_encode($this->JSON);
if ($result === false) {
return (string) json_encode([
'success' => false,
'error' => 'JSON encoding failed: ' . json_last_error_msg(),
]);
}
return $result;
}
/**
* Sends an HTML response to the browser
*/
public function response(): void
{
$buffer = OutputBuffering::getInstance();
if (empty($this->HTML)) {
$this->HTML = $buffer->getContents();
}
if ($this->isAjax()) {
echo $this->ajaxResponse();
} else {
echo $this->getDisplay();
}
$buffer->flush();
exit;
}
/**
* Wrapper around PHP's header() function.
*
* @param string $text header string
*/
public function header($text): void
{
// phpcs:ignore SlevomatCodingStandard.Namespaces.ReferenceUsedNamesOnly
\header($text);
}
/**
* Wrapper around PHP's headers_sent() function.
*/
public function headersSent(): bool
{
return headers_sent();
}
/**
* Wrapper around PHP's http_response_code() function.
*
* @param int $response_code will set the response code.
*/
public function httpResponseCode($response_code): void
{
http_response_code($response_code);
}
/**
* Sets http response code.
*
* @param int $responseCode will set the response code.
*/
public function setHttpResponseCode(int $responseCode): void
{
$this->httpResponseCode($responseCode);
$header = 'status: ' . $responseCode . ' ';
if (isset(static::$httpStatusMessages[$responseCode])) {
$header .= static::$httpStatusMessages[$responseCode];
} else {
$header .= 'Web server is down';
}
if (PHP_SAPI === 'cgi-fcgi') {
return;
}
$this->header($header);
}
/**
* Generate header for 303
*
* @param string $location will set location to redirect.
*/
public function generateHeader303($location): void
{
$this->setHttpResponseCode(303);
$this->header('Location: ' . $location);
if (! defined('TESTSUITE')) {
exit;
}
}
/**
* Configures response for the login page
*
* @return bool Whether caller should exit
*/
public function loginPage(): bool
{
/* Handle AJAX redirection */
if ($this->isAjax()) {
$this->setRequestStatus(false);
// redirect_flag redirects to the login page
$this->addJSON('redirect_flag', '1');
return true;
}
$this->getFooter()->setMinimal();
$header = $this->getHeader();
$header->setBodyId('loginform');
$header->setTitle('phpMyAdmin');
$header->disableMenuAndConsole();
$header->disableWarnings();
return false;
}
}
修正完了です
回答
確認いただきありがとうございます。次に、デバッグメッセージを使用して、問題の根本原因を特定する手順について説明します。
### デバッグ手順
1. **デバッグメッセージの追加**
`ResponseRenderer` クラスの `getDisplay()` メソッドにデバッグメッセージを追加しましたが、エラーの詳細をさらに調査するため、メッセージを調整し、クラスの初期化部分にも追加します。
### 更新された `ResponseRenderer` クラスのコード
php
private function getDisplay(): string
{
$retval = '';
if ($this->header !== null) {
$retval .= "Header exists: " . get_class($this->header) . "\n";
try {
$retval .= $this->header->getDisplay();
} catch (\Throwable $e) {
$retval .= "Error in header getDisplay: " . $e->getMessage() . "\n";
}
} else {
$retval .= "Header object is null.\n";
}
$retval .= $this->HTML;
if ($this->footer !== null) {
$retval .= "Footer exists: " . get_class($this->footer) . "\n";
try {
$retval .= $this->footer->getDisplay();
} catch (\Throwable $e) {
$retval .= "Error in footer getDisplay: " . $e->getMessage() . "\n";
}
} else {
$retval .= "Footer object is null.\n";
}
return $retval;
}
### 手順
1. 変更内容を保存し、オートロードファイルを再生成します。
2. Apacheを再起動して変更を適用します。
3. エラーログを再確認して詳細な情報を取得します。
具体的には次の手順を実行してください:
bash
# Composerのオートロードファイルを再生成
sudo composer dump-autoload
# Apacheを再起動
sudo systemctl restart apache2
# エラーログを確認
sudo tail -n 50 /var/log/apache2/error.log
### 期待する結果
1. エラーログに追加したデバッグメッセージが表示され、新たな詳細情報が記録されていることを確認。
2. `header` や `footer` の `getDisplay()` メソッドが正常に実行されるかどうか、もしくはエラーメッセージが記録される。
これにより、問題の根本原因を特定し、修正を行うための情報が得られるはずです。新しいエラーメッセージや詳細な情報が得られた場合、その内容を共有してください。さらなるサポートを提供いたします。
質問
owner@Linux-for-owner:/usr/share/php$ sudo composer dump-autoload
[sudo] owner のパスワード:
Do not run Composer as root/super user! See https://getcomposer.org/root for details
Continue as root/super user [yes]?
Generating autoload files
Generated autoload files
owner@Linux-for-owner:/usr/share/php$
owner@Linux-for-owner:/usr/share/php$ sudo systemctl restart apache2
[sudo] owner のパスワード:
owner@Linux-for-owner:/usr/share/php$ sudo tail -n 20 /var/log/apache2/error.log[Wed Sep 18 19:24:48.733580 2024] [mpm_prefork:notice] [pid 1608] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:24:48.833389 2024] [mpm_prefork:notice] [pid 6704] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:24:48.833440 2024] [core:notice] [pid 6704] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 19:27:30.762036 2024] [mpm_prefork:notice] [pid 6704] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:27:30.873019 2024] [mpm_prefork:notice] [pid 6957] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:27:30.873090 2024] [core:notice] [pid 6957] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 19:27:37.810780 2024] [php:error] [pid 6959] [client 127.0.0.1:44218] PHP Fatal error: Uncaught Error: Class "PhpMyAdmin\\Twig\\Extensions\\I18nExtension" not found in /usr/share/phpmyadmin/libraries/classes/Twig/I18nExtension.php:11\nStack trace:\n#0 /usr/share/phpmyadmin/autoload.php(652): require()\n#1 /usr/share/phpmyadmin/libraries/classes/Template.php(98): {closure}()\n#2 /usr/share/phpmyadmin/libraries/classes/Template.php(62): PhpMyAdmin\\Template::getTwigEnvironment()\n#3 /usr/share/phpmyadmin/libraries/classes/Header.php(107): PhpMyAdmin\\Template->__construct()\n#4 /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php(168): PhpMyAdmin\\Header->__construct()\n#5 /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php(199): PhpMyAdmin\\ResponseRenderer->__construct()\n#6 /usr/share/phpmyadmin/libraries/classes/ErrorHandler.php(335): PhpMyAdmin\\ResponseRenderer::getInstance()\n#7 /usr/share/phpmyadmin/libraries/classes/ErrorHandler.php(307): PhpMyAdmin\\ErrorHandler->dispFatalError()\n#8 /usr/share/phpmyadmin/libraries/classes/ErrorHandler.php(237): PhpMyAdmin\\ErrorHandler->addError()\n#9 [internal function]: PhpMyAdmin\\ErrorHandler->handleException()\n#10 {main}\n thrown in /usr/share/phpmyadmin/libraries/classes/Twig/I18nExtension.php on line 11
[Wed Sep 18 19:27:37.810844 2024] [php:error] [pid 6959] [client 127.0.0.1:44218] PHP Fatal error: Uncaught Error: Call to a member function getDisplay() on null in /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php:293\nStack trace:\n#0 /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php(411): PhpMyAdmin\\ResponseRenderer->getDisplay()\n#1 [internal function]: PhpMyAdmin\\ResponseRenderer->response()\n#2 {main}\n thrown in /usr/share/phpmyadmin/libraries/classes/ResponseRenderer.php on line 293
[Wed Sep 18 19:40:00.803499 2024] [mpm_prefork:notice] [pid 6957] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:40:00.917683 2024] [mpm_prefork:notice] [pid 7408] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:40:00.917749 2024] [core:notice] [pid 7408] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 19:50:24.845510 2024] [mpm_prefork:notice] [pid 7408] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 19:50:24.988632 2024] [mpm_prefork:notice] [pid 7725] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 19:50:24.988720 2024] [core:notice] [pid 7725] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 20:08:39.295242 2024] [mpm_prefork:notice] [pid 7725] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 20:08:39.424069 2024] [mpm_prefork:notice] [pid 8033] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 20:08:39.424144 2024] [core:notice] [pid 8033] AH00094: Command line: '/usr/sbin/apache2'
[Wed Sep 18 20:57:28.022811 2024] [mpm_prefork:notice] [pid 8033] AH00170: caught SIGWINCH, shutting down gracefully
[Wed Sep 18 20:57:28.148987 2024] [mpm_prefork:notice] [pid 8842] AH00163: Apache/2.4.58 (Ubuntu) configured -- resuming normal operations
[Wed Sep 18 20:57:28.149049 2024] [core:notice] [pid 8842] AH00094: Command line: '/usr/sbin/apache2'
owner@Linux-for-owner:/usr/share/php$
エラーログに何も書いてない気がするのですが。エラー500のままです。ResponseRendererは修正済みです。今21:00ですし、20:57分のログは3行しかありません。
回答
エラーログに新しい情報が表示されない場合、次に確認することは以下の通りです。`ErrorHandler` クラスやアプリケーション全体の設定に問題がある可能性を考慮し、さらに調査を進めます。
### 手順1: エラーログの設定確認
まず、Apacheのエラーログ設定を確認し、詳細なログが出力されるように設定を確認します。
Apacheの設定ファイルを開きます。
bash
sudo nano /etc/apache2/apache2.conf
次に、以下の設定が含まれていることを確認:
conf
LogLevel warn
ErrorLog ${APACHE_LOG_DIR}/error.log
詳細なログを出力するために、LogLevelを `error` から `debug` に変更します:
conf
LogLevel debug
変更を保存し、Apacheを再起動します。
bash
sudo systemctl restart apache2
### 手順2: PHPのエラーログ設定を確認
次に、PHPの設定ファイル(php.ini)を確認し、エラーログが記録されるように設定します。
bash
sudo nano /etc/php/8.0/apache2/php.ini
以下の設定を確認・変更します:
conf
display_errors = Off
log_errors = On
error_log = /var/log/php_errors.log
変更を保存し、Apacheを再起動します。
bash
sudo systemctl restart apache2
### 手順3: エラーハンドラの確認
エラーハンドラが適切に設定されていることを確認します。`ErrorHandler` クラスが正常に作動しているか、全体の設定を見直します。
以下の commands を使用して、問題の詳細な情報がログに出力されるようにします:
bash
sudo tail -n 50 /var/log/apache2/error.log
sudo tail -n 50 /var/log/php_errors.log
### 手順4: `ErrorHandler` クラスの確認
`ErrorHandler` クラスの設定と初期化を確認します。このクラスが正しく動作しているか確認し、エラーメッセージが正しくキャッチされていることを確認します。
bash
sudo nano /usr/share/phpmyadmin/libraries/classes/ErrorHandler.php
### `ErrorHandler.php` の内容確認
エラーが捕捉されているか以下のようにログに記録されているかを確認します。
php
class ErrorHandler
{
public function __construct()
{
set_error_handler([$this, 'handleError']);
set_exception_handler([$this, 'handleException']);
register_shutdown_function([$this, 'handleShutdown']);
ini_set('display_errors', 'Off');
ini_set('log_errors', 'On');
ini_set('error_log', '/var/log/php_errors.log');
}
public function handleError($errno, $errstr, $errfile, $errline)
{
// エラーハンドラの内容をデバッグし、必要に応じて修正します
error_log("Error [{$errno}]: {$errstr} in {$errfile} on line {$errline}");
}
public function handleException($exception)
{
// 例外ハンドラの内容をデバッグし、必要に応じて修正します
error_log("Exception: " . $exception->getMessage());
}
public function handleShutdown()
{
$error = error_get_last();
if ($error !== null) {
$this->handleError($error['type'], $error['message'], $error['file'], $error['line']);
}
}
}
### ApacheとPHPの再起動および動作確認
再度以下の手順を実行し、エラーログを確認します。
bash
# Apacheの再起動
sudo systemctl restart apache2
# エラーログの確認
sudo tail -n 50 /var/log/apache2/error.log
sudo tail -n 50 /var/log/php_errors.log
これにより、ログが適切に出力され、問題の原因が特定されることを期待します。新しい情報が得られた場合、その詳細を共有してください。さらにサポートを提供します。