Merge remote-tracking branch 'origin/master'
[phpmyadmin.git] / libraries / error_report.lib.php
bloba7184b05bd6415c68aeee119853fcf3d95efea3b
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 if (! defined('PHPMYADMIN')) {
10 exit;
13 /**
14 * The generated file that contains the line numbers for the js files
15 * If you change any of the js files you can run the scripts/line-counts.sh
17 if (is_readable('js/line_counts.php')) {
18 include_once 'js/line_counts.php';
21 /**
22 * the url where to submit reports to
24 define('SUBMISSION_URL', "https://reports.phpmyadmin.net/incidents/create");
26 /**
27 * returns the pretty printed error report data collected from the
28 * current configuration or from the request parameters sent by the
29 * error reporting js code.
31 * @return String the report
33 function PMA_getPrettyReportData()
35 $report = PMA_getReportData();
37 return json_encode($report, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
40 /**
41 * returns the error report data collected from the current configuration or
42 * from the request parameters sent by the error reporting js code.
44 * @param string $exception_type whether exception is 'js' or 'php'
46 * @return array error report if success, Empty Array otherwise
48 function PMA_getReportData($exception_type = 'js')
50 $relParams = PMA_getRelationsParam();
51 // common params for both, php & js exceptions
52 $report = array(
53 "pma_version" => PMA_VERSION,
54 "browser_name" => PMA_USR_BROWSER_AGENT,
55 "browser_version" => PMA_USR_BROWSER_VER,
56 "user_os" => PMA_USR_OS,
57 "server_software" => $_SERVER['SERVER_SOFTWARE'],
58 "user_agent_string" => $_SERVER['HTTP_USER_AGENT'],
59 "locale" => $_COOKIE['pma_lang'],
60 "configuration_storage" =>
61 is_null($relParams['db']) ? "disabled" :
62 "enabled",
63 "php_version" => phpversion()
66 if ($exception_type == 'js') {
67 if (empty($_REQUEST['exception'])) {
68 return array();
70 $exception = $_REQUEST['exception'];
71 $exception["stack"] = PMA_translateStacktrace($exception["stack"]);
72 List($uri, $script_name) = PMA_sanitizeUrl($exception["url"]);
73 $exception["uri"] = $uri;
74 unset($exception["url"]);
76 $report ["exception_type"] = 'js';
77 $report ["exception"] = $exception;
78 $report ["script_name"] = $script_name;
79 $report ["microhistory"] = $_REQUEST['microhistory'];
81 if (! empty($_REQUEST['description'])) {
82 $report['steps'] = $_REQUEST['description'];
84 } elseif ($exception_type == 'php') {
85 $errors = array();
86 // create php error report
87 $i = 0;
88 if (!isset($_SESSION['prev_errors'])
89 || $_SESSION['prev_errors'] == ''
90 ) {
91 return array();
93 foreach ($_SESSION['prev_errors'] as $errorObj) {
94 /* @var $errorObj PMA\libraries\Error */
95 if ($errorObj->getLine()
96 && $errorObj->getType()
97 && $errorObj->getNumber() != E_USER_WARNING
98 ) {
99 $errors[$i++] = array(
100 "lineNum" => $errorObj->getLine(),
101 "file" => $errorObj->getFile(),
102 "type" => $errorObj->getType(),
103 "msg" => $errorObj->getOnlyMessage(),
104 "stackTrace" => $errorObj->getBacktrace(5),
105 "stackhash" => $errorObj->getHash()
111 // if there were no 'actual' errors to be submitted.
112 if ($i==0) {
113 return array(); // then return empty array
115 $report ["exception_type"] = 'php';
116 $report["errors"] = $errors;
117 } else {
118 return array();
121 return $report;
125 * Sanitize a url to remove the identifiable host name and extract the
126 * current script name from the url fragment
128 * It returns two things in an array. The first is the uri without the
129 * hostname and identifying query params. The second is the name of the
130 * php script in the url
132 * @param String $url the url to sanitize
134 * @return array the uri and script name
136 function PMA_sanitizeUrl($url)
138 $components = parse_url($url);
139 if (isset($components["fragment"])
140 && preg_match("<PMAURL-\d+:>", $components["fragment"], $matches)
142 $uri = str_replace($matches[0], "", $components["fragment"]);
143 $url = "http://dummy_host/" . $uri;
144 $components = parse_url($url);
147 // get script name
148 preg_match("<([a-zA-Z\-_\d]*\.php)$>", $components["path"], $matches);
149 if (count($matches) < 2) {
150 $script_name = 'index.php';
151 } else {
152 $script_name = $matches[1];
155 // remove deployment specific details to make uri more generic
156 if (isset($components["query"])) {
157 parse_str($components["query"], $query_array);
158 unset($query_array["db"]);
159 unset($query_array["table"]);
160 unset($query_array["token"]);
161 unset($query_array["server"]);
162 $query = http_build_query($query_array);
163 } else {
164 $query = '';
167 $uri = $script_name . "?" . $query;
168 return array($uri, $script_name);
172 * Sends report data to the error reporting server
174 * @param array $report the report info to be sent
176 * @return String the reply of the server
178 function PMA_sendErrorReport($report)
180 $data_string = json_encode($report);
181 if (ini_get('allow_url_fopen')) {
182 $context = array("http" =>
183 array(
184 'method' => 'POST',
185 'content' => $data_string,
186 'header' => "Content-Type: multipart/form-data\r\n",
189 $context = PMA\libraries\Util::handleContext($context);
190 $response = @file_get_contents(
191 SUBMISSION_URL,
192 false,
193 stream_context_create($context)
195 return $response;
198 if (!function_exists('curl_init')) {
199 return null;
202 $curl_handle = curl_init(SUBMISSION_URL);
203 if ($curl_handle === false) {
204 return null;
206 $curl_handle = PMA\libraries\Util::configureCurl($curl_handle);
207 curl_setopt($curl_handle, CURLOPT_CUSTOMREQUEST, "POST");
208 curl_setopt($curl_handle, CURLOPT_HTTPHEADER, array('Expect:'));
209 curl_setopt($curl_handle, CURLOPT_POSTFIELDS, $data_string);
210 curl_setopt($curl_handle, CURLOPT_RETURNTRANSFER, 1);
211 $response = curl_exec($curl_handle);
212 curl_close($curl_handle);
214 return $response;
218 * Returns number of lines in given javascript file.
220 * @param string $filename javascript filename
222 * @return Number of lines
224 * @todo Should gracefully handle non existing files
226 function PMA_countLines($filename)
228 global $LINE_COUNT;
229 if (defined('LINE_COUNTS')) {
230 return $LINE_COUNT[$filename];
233 // ensure that the file is inside the phpMyAdmin folder
234 $depath = 1;
235 foreach (explode('/', $filename) as $part) {
236 if ($part == '..') {
237 $depath--;
238 } elseif ($part != '.') {
239 $depath++;
241 if ($depath < 0) {
242 return 0;
246 $linecount = 0;
247 $handle = fopen('./js/' . $filename, 'r');
248 while (!feof($handle)) {
249 $line = fgets($handle);
250 if ($line === false) {
251 break;
253 $linecount++;
255 fclose($handle);
256 return $linecount;
260 * returns the translated line number and the file name from the cumulative line
261 * number and an array of files
263 * uses the $LINE_COUNT global array of file names and line numbers
265 * @param array $filenames list of files in order of concatenation
266 * @param Integer $cumulative_number the cumulative line number in the
267 * concatenated files
269 * @return array the filename and line number
270 * Returns two variables in an array:
271 * - A String $filename the filename where the requested cumulative number
272 * exists
273 * - Integer $linenumber the translated line number in the returned file
275 function PMA_getLineNumber($filenames, $cumulative_number)
277 $cumulative_sum = 0;
278 foreach ($filenames as $filename) {
279 $filecount = PMA_countLines($filename);
280 if ($cumulative_number <= $cumulative_sum + $filecount + 2) {
281 $linenumber = $cumulative_number - $cumulative_sum;
282 break;
284 $cumulative_sum += $filecount + 2;
286 if (! isset($filename)) {
287 $filename = '';
289 return array($filename, $linenumber);
293 * translates the cumulative line numbers in the stack trace as well as sanitize
294 * urls and trim long lines in the context
296 * @param array $stack the stack trace
298 * @return array $stack the modified stack trace
300 function PMA_translateStacktrace($stack)
302 foreach ($stack as &$level) {
303 foreach ($level["context"] as &$line) {
304 if (mb_strlen($line) > 80) {
305 $line = mb_substr($line, 0, 75) . "//...";
308 if (preg_match("<js/get_scripts.js.php\?(.*)>", $level["url"], $matches)) {
309 parse_str($matches[1], $vars);
310 List($file_name, $line_number) = PMA_getLineNumber(
311 $vars["scripts"], $level["line"]
313 $level["filename"] = $file_name;
314 $level["line"] = $line_number;
315 } else {
316 unset($level["context"]);
317 List($uri, $script_name) = PMA_sanitizeUrl($level["url"]);
318 $level["uri"] = $uri;
319 $level["scriptname"] = $script_name;
321 unset($level["url"]);
323 unset($level);
324 return $stack;
328 * generates the error report form to collect user description and preview the
329 * report before being sent
331 * @return String the form
333 function PMA_getErrorReportForm()
335 $datas = array(
336 'report_data' => PMA_getPrettyReportData(),
337 'hidden_inputs' => PMA_URL_getHiddenInputs(),
338 'hidden_fields' => null,
341 $reportData = PMA_getReportData();
342 if (!empty($reportData)) {
343 $datas['hidden_fields'] = PMA_getHiddenFields($reportData);
346 return PMA\libraries\Template::get('error/report_form')
347 ->render($datas);
351 * generates the error report form to collect user description and preview the
352 * report before being sent
354 * @return String the form
356 function PMA_hasLatestLineCounts()
358 $line_counts_time = filemtime("js/line_counts.php");
359 $js_time = filemtime("js");
360 return $line_counts_time >= $js_time;