Merge pull request #1219 from dhananjay92/dhananjay-gsoc-rebased

Commits for review: Add php Error Reporting Functionality to PMA (Rebased)
This commit is contained in:
Michal Čihař 2014-06-16 11:11:13 +02:00
commit d78e41d737
19 changed files with 471 additions and 83 deletions

View File

@ -7,66 +7,124 @@
*/
require_once 'libraries/common.inc.php';
require_once 'libraries/error_report.lib.php';
require_once 'libraries/user_preferences.lib.php';
if (!isset($_REQUEST['exception_type'])
||!in_array($_REQUEST['exception_type'], array('js', 'php'))
) {
die('Oops, something went wrong!!');
}
$response = PMA_Response::getInstance();
if (isset($_REQUEST['send_error_report'])
&& $_REQUEST['send_error_report'] == true
&& ($_REQUEST['send_error_report'] == true
|| $_REQUEST['send_error_report'] == '1')
) {
$server_response = PMA_sendErrorReport(PMA_getReportData());
if ($server_response === false) {
$success = false;
} else {
$decoded_response = json_decode($server_response, true);
$success = !empty($decoded_response) ? $decoded_response["success"] : false;
}
/* Message to show to the user */
if ($success) {
if (isset($_REQUEST['automatic'])
&& $_REQUEST['automatic'] === "true"
if ($_REQUEST['exception_type'] == 'php') {
/**
* Prevent inifnite error submission.
* Happens in case error submissions fails.
* If reporting is done in some time interval,
* just clear them & clear json data too.
*/
if (isset($_SESSION['prev_error_subm_time'])
&& isset($_SESSION['error_subm_count'])
&& $_SESSION['error_subm_count'] >= 3
&& ($_SESSION['prev_error_subm_time']-time()) <= 3000
) {
$message = __(
'An error has been detected and an error report has been '
. 'automatically submitted based on your settings.'
);
$_SESSION['error_subm_count'] = 0;
$_SESSION['prev_errors'] = '';
$response = PMA_Response::getInstance();
$response->addJSON('_stopErrorReportLoop', '1');
} else {
$message = __('Thank you for submitting this report.');
$_SESSION['prev_error_subm_time'] = time();
$_SESSION['error_subm_count'] = (
(isset($_SESSION['error_subm_count']))
? ($_SESSION['error_subm_count']+1)
: (0)
);
}
} else {
$message = __(
'An error has been detected and an error report has been '
. 'generated but failed to be sent.'
)
. ' '
. __(
'If you experience any '
. 'problems please submit a bug report manually.'
);
}
$message .= ' ' . __('You may want to refresh the page.');
$reportData = PMA_getReportData($_REQUEST['exception_type']);
// report if and only if there were 'actual' errors.
if (count($reportData) > 0) {
$server_response = PMA_sendErrorReport($reportData);
if ($server_response === false) {
$success = false;
} else {
$decoded_response = json_decode($server_response, true);
$success = !empty($decoded_response) ?
$decoded_response["success"] : false;
}
/* Create message object */
if ($success) {
$message = PMA_Message::notice($message);
} else {
$message = PMA_Message::error($message);
}
/* Message to show to the user */
if ($success) {
if ((isset($_REQUEST['automatic'])
&& $_REQUEST['automatic'] === "true")
|| $GLOBALS['cfg']['SendErrorReports'] == 'always'
) {
$msg = __(
'An error has been detected and an error report has been '
. 'automatically submitted based on your settings.'
);
} else {
$msg = __('Thank you for submitting this report.');
}
} else {
$msg = __(
'An error has been detected and an error report has been '
. 'generated but failed to be sent.'
)
. ' '
. __(
'If you experience any '
. 'problems please submit a bug report manually.'
);
}
$msg .= ' ' . __('You may want to refresh the page.');
/* Add message to JSON response */
$response->addJSON('message', $message);
/* Create message object */
if ($success) {
$msg = PMA_Message::notice($msg);
} else {
$msg = PMA_Message::error($msg);
}
/* Persist always send settings */
if (! isset($_REQUEST['automatic'])
&& $_REQUEST['automatic'] !== "true"
&& isset($_REQUEST['always_send'])
&& $_REQUEST['always_send'] === "true"
) {
PMA_persistOption("SendErrorReports", "always", "ask");
/* Add message to response */
if ($response->isAjax()) {
if ($_REQUEST['exception_type'] == 'js') {
$response->addJSON('message', $msg);
} else {
$response->addJSON('_errSubmitMsg', $msg);
}
} elseif ($_REQUEST['exception_type'] == 'php') {
$jsCode = 'PMA_ajaxShowMessage("<div class=\"error\">'
. $msg
. '</div>", false);';
$response->getFooter()->getScripts()->addCode($jsCode);
}
if ($_REQUEST['exception_type'] == 'php') {
// clear previous errors & save new ones.
$GLOBALS['error_handler']->savePreviousErrors();
}
/* Persist always send settings */
if (isset($_REQUEST['always_send'])
&& $_REQUEST['always_send'] === "true"
) {
PMA_persistOption("SendErrorReports", "always", "ask");
}
}
} elseif (! empty($_REQUEST['get_settings'])) {
$response->addJSON('report_setting', $GLOBALS['cfg']['SendErrorReports']);
} else {
$response->addHTML(PMA_getErrorReportForm());
if ($_REQUEST['exception_type'] == 'js') {
$response->addHTML(PMA_getErrorReportForm());
} else {
// clear previous errors & save new ones.
$GLOBALS['error_handler']->savePreviousErrors();
}
}
?>

View File

@ -388,11 +388,47 @@ var AJAX = {
}
$('#pma_errors').remove();
var msg = '';
if(data._errSubmitMsg){
msg = data._errSubmitMsg;
}
if (data._errors) {
$('<div/>', {id : 'pma_errors'})
.insertAfter('#selflink')
.append(data._errors);
// bind for php error reporting forms (bottom)
$("#pma_ignore_errors_bottom").bind("click",
function() {
PMA_ignorePhpErrors();
});
$("#pma_ignore_all_errors_bottom").bind("click",
function() {
PMA_ignorePhpErrors(false);
});
// In case of 'sendErrorReport'='always'
// submit the hidden error reporting form.
if (data._sendErrorAlways == '1'
&& data._stopErrorReportLoop != '1'
) {
$("#pma_report_errors_form").submit();
PMA_ajaxShowMessage(PMA_messages['phpErrorsBeingSubmitted'], false);
$('html, body').animate({scrollTop:$(document).height()}, 'slow');
} else if (data._promptPhpErrors) {
// otherwise just prompt user if it is set so.
msg = msg + PMA_messages['phpErrorsFound'];
// scroll to bottom where all the erros are displayed.
$('html, body').animate({scrollTop:$(document).height()}, 'slow');
}
}
PMA_ajaxShowMessage(msg, false);
// bind for php error reporting forms (popup)
$("#pma_ignore_errors_popup").bind("click", function() {
PMA_ignorePhpErrors()
});
$("#pma_ignore_all_errors_popup").bind("click", function() {
PMA_ignorePhpErrors(false)
});
if (typeof AJAX._callback === 'function') {
AJAX._callback.call();

View File

@ -23,7 +23,8 @@ var ErrorReport = {
ajax_request: true,
server: PMA_commonParams.get('server'),
token: PMA_commonParams.get('token'),
get_settings: true
get_settings: true,
exception_type: 'js'
}, function (data) {
if (data.success !== true) {
PMA_ajaxShowMessage(data.error, false);
@ -227,7 +228,8 @@ var ErrorReport = {
"token": PMA_commonParams.get('token'),
"exception": exception,
"current_url": window.location.href,
"microhistory": ErrorReport._get_microhistory()
"microhistory": ErrorReport._get_microhistory(),
"exception_type": 'js'
};
if (typeof AJAX.cache.pages[AJAX.cache.current - 1] !== 'undefined') {
report_data.scripts = AJAX.cache.pages[AJAX.cache.current - 1].scripts.map(

View File

@ -4379,3 +4379,29 @@ function PMA_previewSQL($form)
}
});
}
/**
* Ignore the displayed php errors.
* Simply removes the displayed errors.
*
* @param clearPrevErrors whether to clear errors stored
* in $_SESSION['prev_errors'] at server
*
*/
function PMA_ignorePhpErrors(clearPrevErrors){
if (typeof(clearPrevErrors) === "undefined"
|| clearPrevErrors === null
) {
str = false;
}
// send AJAX request to error_report.php with send_error_report=0, exception_type=php & token.
// It clears the prev_errors stored in session.
if(clearPrevErrors){
$('#pma_report_errors_form input[name="send_error_report"]').val(0); // change send_error_report to '0'
$('#pma_report_errors_form').submit();
}
// remove dislayed errors
$('#pma_errors').fadeOut( "slow");
$('#pma_errors').remove();
}

View File

@ -426,6 +426,29 @@ $js_messages['strTooManyInputs'] = __(
. "max_input_vars configuration."
);
$js_messages['phpErrorsFound'] = '<div class="error">'
. __('Warning: Some errors have been detected on the server!!')
. '<div>'
. __('Please look at the bottom of this window.')
. '<input id="pma_ignore_errors_popup" type="submit" value="'
. __('Ignore')
. '" style="float: right; margin: 20px;">'
. '<input id="pma_ignore_all_errors_popup" type="submit" value="'
. __('Ignore All')
. '" style="float: right; margin: 20px;">'
. '</div></div>';
$js_messages['phpErrorsBeingSubmitted'] = '<div class="error">'
. __('Some errors have been detected on the server!!')
. '<br/>'
. __('As per your settings, they are being submitted currently.')
. __(' Please be patient.')
. '<br/>'
. '<img src="'
. ($_SESSION['PMA_Theme']->getImgPath('ajax_clock_small.gif'))
. '" width="16" height="16" alt="ajax clock"/>'
. '</div>';
echo "var PMA_messages = new Array();\n";
foreach ($js_messages as $name => $js_message) {
PMA_printJsValue("PMA_messages['" . $name . "']", $js_message);

View File

@ -188,12 +188,19 @@ class PMA_Error extends PMA_Message
}
/**
* returns PMA_Error::$_backtrace
* returns PMA_Error::$_backtrace for first $count frames
* pass $count = -1 to get full backtrace.
* The same can be done by not passing $count at all.
*
* @param integer $count Number of stack frames.
*
* @return array PMA_Error::$_backtrace
*/
public function getBacktrace()
public function getBacktrace($count = -1)
{
if ($count != -1) {
return array_slice($this->backtrace, 0, $count);
}
return $this->backtrace;
}

View File

@ -94,6 +94,17 @@ class PMA_Error_Handler
return $this->errors;
}
/**
* returns the errors occured in the current run only.
* Does not include the errors save din the SESSION
*
* @return array of current errors
*/
public function getCurrentErrors()
{
return $this->errors;
}
/**
* Error handler - called when errors are triggered/occurred
*
@ -281,7 +292,10 @@ class PMA_Error_Handler
public function getDispErrors()
{
$retval = '';
if ($GLOBALS['cfg']['Error_Handler']['display']) {
// display errors if SendErrorReports is set to 'ask'.
if ($GLOBALS['cfg']['SendErrorReports'] != 'never'
|| $GLOBALS['cfg']['Error_Handler']['display']
) {
foreach ($this->getErrors() as $error) {
if ($error instanceof PMA_Error) {
if (! $error->isDisplayed()) {
@ -297,6 +311,46 @@ class PMA_Error_Handler
} else {
$retval .= $this->getDispUserErrors();
}
// if preference is not 'never' and
// there are 'actual' errors to be reported
if ($GLOBALS['cfg']['SendErrorReports'] != 'never'
&& $this->countErrors() != $this->countUserErrors()
) {
// add report button.
$retval .= '<form method="post" action="error_report.php"'
. ' id="pma_report_errors_form"';
if ($GLOBALS['cfg']['SendErrorReports'] == 'always') {
// in case of 'always', generate 'invisible' form.
$retval .= ' style="display:none;"';
}
$retval .= '>'
. '<input type="hidden" name="token" value="'
. $_SESSION[' PMA_token ']
. '"/>'
. '<input type="hidden" name="exception_type" value="php"/>'
. '<input type="hidden" name="send_error_report" value="1" />'
. '<input type="submit" value="'
. __('Report')
. '" id="pma_report_errors" style="float: right; margin: 20px;">'
. '<input type="checkbox" name="always_send"'
. ' id="always_send_checkbox" value="true"/>'
. '<label for="always_send_checkbox">'
. __('Automatically send report next time')
. '</label>'
. '</form>';
if ($GLOBALS['cfg']['SendErrorReports'] == 'ask') {
// add ignore buttons
$retval .= '<input type="submit" value="'
. __('Ignore')
. '" id="pma_ignore_errors_bottom"'
. ' style="float: right; margin: 20px;">';
}
$retval .= '<input type="submit" value="'
. __('Ignore All')
. '" id="pma_ignore_all_errors_bottom"'
. ' style="float: right; margin: 20px;">';
}
return $retval;
}
@ -389,7 +443,9 @@ class PMA_Error_Handler
*/
public function countDisplayErrors()
{
if ($GLOBALS['cfg']['Error_Handler']['display']) {
if ($GLOBALS['cfg']['SendErrorReports'] != 'never'
|| $GLOBALS['cfg']['Error_Handler']['display']
) {
return $this->countErrors();
} else {
return $this->countUserErrors();
@ -405,5 +461,102 @@ class PMA_Error_Handler
{
return (bool) $this->countDisplayErrors();
}
/**
* Deletes prevsiously stored errors in SESSION.
* Saves current errors in session as previous errros.
* Required to save current errors in case 'ask'
*
* @return void
*/
public function savePreviousErrors()
{
unset($_SESSION['prev_errors']);
$_SESSION['prev_errors'] = $GLOBALS['error_handler']->getCurrentErrors();
}
/**
* Function to check if there are any errors to be prompted.
* Needed because user warnings raised are
* also collected by global error handler.
* This dishtingushes between the actual errors
* and user errors raised to warn user.
*
*@return boolean: true if there are errors to be "prompted", false otherwise
*/
public function hasErrorsForPrompt()
{
return (
($GLOBALS['cfg']['SendErrorReports'] != 'never'
|| $GLOBALS['cfg']['Error_Handler']['display'])
&& $this->countErrors() != $this->countUserErrors()
);
}
/**
* Function to report all the collected php errors.
* Must be called at the end of each script
* by the $GLOBALS['error_handler'] only.
*
* @return void
*/
public function reportErrors()
{
// if there're no actual errors,
if (!$this->hasErrors()
|| $this->countErrors() == $this->countUserErrors()
) {
// then simply return.
return;
}
// Delete all the prev_errors in session & store new prev_errors in session
$this->savePreviousErrors();
$response = PMA_Response::getInstance();
$jsCode = '';
if ($GLOBALS['cfg']['SendErrorReports'] == 'always') {
if ($response->isAjax()) {
// set flag for automatic report submission.
$response->addJSON('_sendErrorAlways', '1');
} else {
// send the error reports asynchronously & without asking user
$jsCode .= '$("#pma_report_errors_form").submit();'
. 'PMA_ajaxShowMessage(
PMA_messages["phpErrorsBeingSubmitted"], false
);';
// js code to appropriate focusing,
$jsCode .= '$("html, body").animate({
scrollTop:$(document).height()
}, "slow");';
}
} elseif ($GLOBALS['cfg']['SendErrorReports'] == 'ask') {
//ask user whether to submit errors or not.
if (!$response->isAjax()) {
// js code to show appropriate msgs, event binding & focusing.
$jsCode = 'PMA_ajaxShowMessage(PMA_messages["phpErrorsFound"], '
. ' 2000);'
. '$("#pma_ignore_errors_popup").bind("click", function() {
PMA_ignorePhpErrors()
});'
. '$("#pma_ignore_all_errors_popup").bind("click",
function() {
PMA_ignorePhpErrors(false)
});'
. '$("#pma_ignore_errors_bottom").bind("click", function() {
PMA_ignorePhpErrors()
});'
. '$("#pma_ignore_all_errors_bottom").bind("click",
function() {
PMA_ignorePhpErrors(false)
});'
. '$("html, body").animate({
scrollTop:$(document).height()
}, "slow");';
}
}
// The errors are already sent from the resnpose.
// Just focus on errors division upon load event.
$response->getFooter()->getScripts()->addCode($jsCode);
}
}
?>

View File

@ -209,6 +209,12 @@ class PMA_Footer
$retval .= $GLOBALS['error_handler']->getDispErrors();
$retval .= '</div>';
}
/**
* Report php errors
*/
$GLOBALS['error_handler']->reportErrors();
return $retval;
}

View File

@ -655,6 +655,17 @@ class PMA_Message
return $message;
}
/**
* Returns only message string without image & other HTML.
*
* @return $message string
*/
public function getOnlyMessage()
{
return $this->message;
}
/**
* returns PMA_Message::$string
*

View File

@ -316,6 +316,9 @@ class PMA_Response
if (strlen($errors)) {
$this->addJSON('_errors', $errors);
}
$promptPhpErrors = $GLOBALS['error_handler']->hasErrorsForPrompt();
$this->addJSON('_promptPhpErrors', $promptPhpErrors);
if (empty($GLOBALS['error_message'])) {
// set current db, table and sql query in the querywindow
$query = '';

View File

@ -52,37 +52,80 @@ function PMA_getPrettyReportData()
* returns the error report data collected from the current configuration or
* from the request parameters sent by the error reporting js code.
*
* @return Array the report
* @param string $exception_type whether exception is 'js' or 'php'
*
* @return Array error report if success, Empty Array otherwise
*/
function PMA_getReportData()
function PMA_getReportData($exception_type = 'js')
{
if (empty($_REQUEST['exception'])) {
return array();
}
$exception = $_REQUEST['exception'];
$exception["stack"] = PMA_translateStacktrace($exception["stack"]);
List($uri, $script_name) = PMA_sanitizeUrl($exception["url"]);
$exception["uri"] = $uri;
unset($exception["url"]);
$relParams = PMA_getRelationsParam();
// common params for both, php & js execptions
$report = array(
"exception" => $exception,
"script_name" => $script_name,
"pma_version" => PMA_VERSION,
"browser_name" => PMA_USR_BROWSER_AGENT,
"browser_version" => PMA_USR_BROWSER_VER,
"user_os" => PMA_USR_OS,
"server_software" => $_SERVER['SERVER_SOFTWARE'],
"user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
"locale" => $_COOKIE['pma_lang'],
"configuration_storage" =>
empty($GLOBALS['cfg']['Servers'][1]['pmadb']) ? "disabled" :
"enabled",
"php_version" => phpversion(),
"microhistory" => $_REQUEST['microhistory'],
);
"pma_version" => PMA_VERSION,
"browser_name" => PMA_USR_BROWSER_AGENT,
"browser_version" => PMA_USR_BROWSER_VER,
"user_os" => PMA_USR_OS,
"server_software" => $_SERVER['SERVER_SOFTWARE'],
"user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
"locale" => $_COOKIE['pma_lang'],
"configuration_storage" =>
is_null($relParams['db']) ? "disabled" :
"enabled",
"php_version" => phpversion()
);
if (! empty($_REQUEST['description'])) {
$report['steps'] = $_REQUEST['description'];
if ($exception_type == 'js') {
if (empty($_REQUEST['exception'])) {
return array();
}
$exception = $_REQUEST['exception'];
$exception["stack"] = PMA_translateStacktrace($exception["stack"]);
List($uri, $script_name) = PMA_sanitizeUrl($exception["url"]);
$exception["uri"] = $uri;
unset($exception["url"]);
$report ["exception_type"] = 'js';
$report ["exception"] = $exception;
$report ["script_name"] = $script_name;
$report ["microhistory"] = $_REQUEST['microhistory'];
if (! empty($_REQUEST['description'])) {
$report['steps'] = $_REQUEST['description'];
}
} elseif ($exception_type == 'php') {
$errors = array();
// create php error report
$i=0;
if (!isset($_SESSION['prev_errors'])
|| $_SESSION['prev_errors'] == ''
) {
return array();
}
foreach ($_SESSION['prev_errors'] as $errorObj) {
if ($errorObj->getLine()
&& $errorObj->getType()
&& $errorObj->getNumber() != E_USER_WARNING
) {
$errors[$i++] = array(
"lineNum" => $errorObj->getLine(),
"file" => $errorObj->getFile(),
"type" => $errorObj->getType(),
"msg" => $errorObj->getOnlyMessage(),
"stackTrace" => $errorObj->getBacktrace(5),
"stackhash" => $errorObj->getHash()
);
}
}
// if there were no 'actual' errors to be submitted.
if ($i==0) {
return array(); // then return empty array
}
$report ["exception_type"] = 'php';
$report["errors"] = $errors;
} else {
return array();
}
return $report;

View File

@ -284,7 +284,7 @@ class AuthenticationCookie extends AuthenticationPlugin
// END Swekey Integration
if ($GLOBALS['error_handler']->hasDisplayErrors()) {
echo '<div>';
echo '<div id="pma_errors">';
$GLOBALS['error_handler']->dispErrors();
echo '</div>';
}

View File

@ -205,3 +205,4 @@ echo '<input type="hidden" name="querydisplay_tab" value="'
. $querydisplay_tab . '" />';
echo '</form>';
echo '</div>';
?>

View File

@ -72,3 +72,4 @@ if (isset($_REQUEST['do'])) {
$user_schema->setAction($_REQUEST['do']);
$user_schema->processUserChoice();
}
?>

View File

@ -32,8 +32,7 @@ $response->addHTML('<div>');
$response->addHTML($ServerStatusData->getMenuHtml());
$response->addHTML(PMA_getHtmlForAdvisor());
$response->addHTML('</div>');
exit;
?>

View File

@ -107,7 +107,7 @@ class PMA_Error_Handler_Test extends PHPUnit_Framework_TestCase
* @param string $output_show expected output if showing of errors is
* enabled
* @param string $output_hide expected output if showing of errors is
* disabled
* disabled and 'sendErrorReports' is set to 'never'
*
* @return void
*
@ -116,6 +116,8 @@ class PMA_Error_Handler_Test extends PHPUnit_Framework_TestCase
public function testGetDispErrorsForDisplayFalse(
$errno, $errstr, $errfile, $errline, $output_show, $output_hide
) {
// TODO: Add other test cases for all combination of 'sendErrorReports'
$GLOBALS['cfg']['SendErrorReports'] = 'never';
$GLOBALS['cfg']['Error_Handler']['gather'] = true;
$GLOBALS['cfg']['Error_Handler']['display'] = false;

View File

@ -152,4 +152,19 @@ class PMA_Error_Test extends PHPUnit_Framework_TestCase
{
$this->assertEquals('Warning: Compile Error', $this->object->getTitle());
}
/**
* Test for getBacktrace
*
* @return void
*/
public function testGetBacktrace()
{
$this->object->setBacktrace(array('bt1','bt2','bt3','bt4'));
// case: full backtrace
$this->assertEquals(array('bt1','bt2','bt3','bt4'), $this->object->getBacktrace());
// case: first 2 frames
$this->assertEquals(array('bt1','bt2'), $this->object->getBacktrace(2));
}
}

View File

@ -284,3 +284,4 @@ $htmlString .= '</form>'
. '</div>';
echo $htmlString;
?>

View File

@ -136,3 +136,4 @@ echo PMA_getDeleteDataOrTableLink(
echo '</ul>';
echo '</fieldset>';
echo '</div>';
?>