Merge branch 'QA_4_4' into QA_4_5
[phpmyadmin.git] / libraries / error_report.lib.php
blob0ac7ce56b80e9df4ac750b5fb0ec84855d4a75c4
1 <?php
2 /* vim: set expandtab sw=4 ts=4 sts=4: */
3 /**
4 * Error reporting functions used to generate and submit error reports
6 * @package PhpMyAdmin
7 */
9 /*
10 * Include for handleContext() and configureCurl in PMA_sendErrorReport()
12 require_once 'libraries/Util.class.php';
15 if (! defined('PHPMYADMIN')) {
16 exit;
19 /**
20 * The generated file that contains the line numbers for the js files
21 * If you change any of the js files you can run the scripts/line-counts.sh
23 if (is_readable('js/line_counts.php')) {
24 include_once 'js/line_counts.php';
27 /**
28 * the url where to submit reports to
30 define('SUBMISSION_URL', "https://reports.phpmyadmin.net/incidents/create");
32 /**
33 * returns the pretty printed error report data collected from the
34 * current configuration or from the request parameters sent by the
35 * error reporting js code.
37 * @return String the report
39 function PMA_getPrettyReportData()
41 $report = PMA_getReportData();
43 /* JSON_PRETTY_PRINT available since PHP 5.4 */
44 if (defined('JSON_PRETTY_PRINT')) {
45 return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
48 return PMA_prettyPrint($report);
51 /**
52 * returns the error report data collected from the current configuration or
53 * from the request parameters sent by the error reporting js code.
55 * @param string $exception_type whether exception is 'js' or 'php'
57 * @return Array error report if success, Empty Array otherwise
59 function PMA_getReportData($exception_type = 'js')
61 $relParams = PMA_getRelationsParam();
62 // common params for both, php & js exceptions
63 $report = array(
64 "pma_version" => PMA_VERSION,
65 "browser_name" => PMA_USR_BROWSER_AGENT,
66 "browser_version" => PMA_USR_BROWSER_VER,
67 "user_os" => PMA_USR_OS,
68 "server_software" => $_SERVER['SERVER_SOFTWARE'],
69 "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
70 "locale" => $_COOKIE['pma_lang'],
71 "configuration_storage" =>
72 is_null($relParams['db']) ? "disabled" :
73 "enabled",
74 "php_version" => phpversion()
77 if ($exception_type == 'js') {
78 if (empty($_REQUEST['exception'])) {
79 return array();
81 $exception = $_REQUEST['exception'];
82 $exception["stack"] = PMA_translateStacktrace($exception["stack"]);
83 List($uri, $script_name) = PMA_sanitizeUrl($exception["url"]);
84 $exception["uri"] = $uri;
85 unset($exception["url"]);
87 $report ["exception_type"] = 'js';
88 $report ["exception"] = $exception;
89 $report ["script_name"] = $script_name;
90 $report ["microhistory"] = $_REQUEST['microhistory'];
92 if (! empty($_REQUEST['description'])) {
93 $report['steps'] = $_REQUEST['description'];
95 } elseif ($exception_type == 'php') {
96 $errors = array();
97 // create php error report
98 $i = 0;
99 if (!isset($_SESSION['prev_errors'])
100 || $_SESSION['prev_errors'] == ''
102 return array();
104 foreach ($_SESSION['prev_errors'] as $errorObj) {
105 /* @var $errorObj PMA_Error */
106 if ($errorObj->getLine()
107 && $errorObj->getType()
108 && $errorObj->getNumber() != E_USER_WARNING
110 $errors[$i++] = array(
111 "lineNum" => $errorObj->getLine(),
112 "file" => $errorObj->getFile(),
113 "type" => $errorObj->getType(),
114 "msg" => $errorObj->getOnlyMessage(),
115 "stackTrace" => $errorObj->getBacktrace(5),
116 "stackhash" => $errorObj->getHash()
122 // if there were no 'actual' errors to be submitted.
123 if ($i==0) {
124 return array(); // then return empty array
126 $report ["exception_type"] = 'php';
127 $report["errors"] = $errors;
128 } else {
129 return array();
132 return $report;
136 * Sanitize a url to remove the identifiable host name and extract the
137 * current script name from the url fragment
139 * It returns two things in an array. The first is the uri without the
140 * hostname and identifying query params. The second is the name of the
141 * php script in the url
143 * @param String $url the url to sanitize
145 * @return Array the uri and script name
147 function PMA_sanitizeUrl($url)
149 $components = parse_url($url);
150 if (isset($components["fragment"])
151 && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
153 $uri = str_replace($matches[0], "", $components["fragment"]);
154 $url = "http://dummy_host/" . $uri;
155 $components = parse_url($url);
158 // get script name
159 preg_match("<([a-zA-Z\-_\d]*\.php)$>", $components["path"], $matches);
160 if (count($matches) < 2) {
161 $script_name = 'index.php';
162 } else {
163 $script_name = $matches[1];
166 // remove deployment specific details to make uri more generic
167 if (isset($components["query"])) {
168 parse_str($components["query"], $query_array);
169 unset($query_array["db"]);
170 unset($query_array["table"]);
171 unset($query_array["token"]);
172 unset($query_array["server"]);
173 $query = http_build_query($query_array);
174 } else {
175 $query = '';
178 $uri = $script_name . "?" . $query;
179 return array($uri, $script_name);
183 * Sends report data to the error reporting server
185 * @param Array $report the report info to be sent
187 * @return String the reply of the server
189 function PMA_sendErrorReport($report)
191 $data_string = json_encode($report);
192 if (ini_get('allow_url_fopen')) {
193 $context = array("http" =>
194 array(
195 'method' => 'POST',
196 'content' => $data_string,
197 'header' => "Content-Type: multipart/form-data\r\n",
200 $context = PMA_Util::handleContext($context);
201 $response = @file_get_contents(
202 SUBMISSION_URL,
203 false,
204 stream_context_create($context)
206 return $response;
209 if (!function_exists('curl_init')) {
210 return null;
213 $curl_handle = curl_init(SUBMISSION_URL);
214 if ($curl_handle === false) {
215 return null;
217 $curl_handle = PMA_Util::configureCurl($curl_handle);
218 curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
219 curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Expect:'));
220 curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $data_string);
221 curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
222 $response = curl_exec($curl_handle);
223 curl_close($curl_handle);
225 return $response;
229 * Returns number of lines in given javascript file.
231 * @param string $filename javascript filename
233 * @return Number of lines
235 * @todo Should gracefully handle non existing files
237 function PMA_countLines($filename)
239 global $LINE_COUNT;
240 if (defined('LINE_COUNTS')) {
241 return $LINE_COUNT[$filename];
244 // ensure that the file is inside the phpMyAdmin folder
245 $depath = 1;
246 foreach (explode('/', $filename) as $part) {
247 if ($part == '..') {
248 $depath--;
249 } elseif ($part != '.') {
250 $depath++;
252 if ($depath < 0) {
253 return 0;
257 $linecount = 0;
258 $handle = fopen('./js/' . $filename, 'r');
259 while (!feof($handle)) {
260 $line = fgets($handle);
261 if ($line === false) {
262 break;
264 $linecount++;
266 fclose($handle);
267 return $linecount;
271 * returns the translated line number and the file name from the cumulative line
272 * number and an array of files
274 * uses the $LINE_COUNT global array of file names and line numbers
276 * @param Array $filenames list of files in order of concatenation
277 * @param Integer $cumulative_number the cumulative line number in the
278 * concatenated files
280 * @return Array the filename and line number
281 * Returns two variables in an array:
282 * - A String $filename the filename where the requested cumulative number
283 * exists
284 * - Integer $linenumber the translated line number in the returned file
286 function PMA_getLineNumber($filenames, $cumulative_number)
288 $cumulative_sum = 0;
289 foreach ($filenames as $filename) {
290 $filecount = PMA_countLines($filename);
291 if ($cumulative_number <= $cumulative_sum + $filecount + 2) {
292 $linenumber = $cumulative_number - $cumulative_sum;
293 break;
295 $cumulative_sum += $filecount + 2;
297 if (! isset($filename)) {
298 $filename = '';
300 return array($filename, $linenumber);
304 * translates the cumulative line numbers in the stack trace as well as sanitize
305 * urls and trim long lines in the context
307 * @param Array $stack the stack trace
309 * @return Array $stack the modified stack trace
311 function PMA_translateStacktrace($stack)
313 foreach ($stack as &$level) {
314 foreach ($level["context"] as &$line) {
315 if (/*overload*/mb_strlen($line) > 80) {
316 $line = /*overload*/mb_substr($line, 0, 75) . "//...";
319 if (preg_match("<js/get_scripts.js.php\?(.*)>", $level["url"], $matches)) {
320 parse_str($matches[1], $vars);
321 List($file_name, $line_number) = PMA_getLineNumber(
322 $vars["scripts"], $level["line"]
324 $level["filename"] = $file_name;
325 $level["line"] = $line_number;
326 } else {
327 unset($level["context"]);
328 List($uri, $script_name) = PMA_sanitizeUrl($level["url"]);
329 $level["uri"] = $uri;
330 $level["scriptname"] = $script_name;
332 unset($level["url"]);
334 unset($level);
335 return $stack;
339 * generates the error report form to collect user description and preview the
340 * report before being sent
342 * @return String the form
344 function PMA_getErrorReportForm()
346 $datas = array(
347 'report_data' => PMA_getPrettyReportData(),
348 'hidden_inputs' => PMA_URL_getHiddenInputs(),
349 'hidden_fields' => null,
352 $reportData = PMA_getReportData();
353 if (!empty($reportData)) {
354 $datas['hidden_fields'] = PMA_getHiddenFields($reportData);
357 include_once './libraries/Template.class.php';
358 return PMA\Template::get('error/report_form')
359 ->render($datas);
363 * generates the error report form to collect user description and preview the
364 * report before being sent
366 * @return String the form
368 function PMA_hasLatestLineCounts()
370 $line_counts_time = filemtime("js/line_counts.php");
371 $js_time = filemtime("js");
372 return $line_counts_time >= $js_time;
376 * pretty print a variable for the user
378 * @param mixed $object the variable to pretty print
379 * @param String $namespace the namespace to use for printing values
381 * @return String the human readable form of the variable
383 function PMA_prettyPrint($object, $namespace="")
385 if (! is_array($object)) {
386 if (empty($namespace)) {
387 return "$object\n";
388 } else {
389 return "$namespace: \"$object\"\n";
392 $output = "";
393 foreach ($object as $key => $value) {
394 if ($namespace == "") {
395 $new_namespace = "$key";
396 } else {
397 $new_namespace = $namespace . "[$key]";
399 $output .= PMA_prettyPrint($value, $new_namespace);
401 return $output;