ChartController.php 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. <?php
  2. declare(strict_types=1);
  3. namespace PhpMyAdmin\Controllers\Table;
  4. use PhpMyAdmin\DatabaseInterface;
  5. use PhpMyAdmin\DbTableExists;
  6. use PhpMyAdmin\Message;
  7. use PhpMyAdmin\Response;
  8. use PhpMyAdmin\SqlParser\Components\Limit;
  9. use PhpMyAdmin\SqlParser\Parser;
  10. use PhpMyAdmin\SqlParser\Statements\SelectStatement;
  11. use PhpMyAdmin\Template;
  12. use PhpMyAdmin\Url;
  13. use PhpMyAdmin\Util;
  14. use function array_keys;
  15. use function htmlspecialchars;
  16. use function in_array;
  17. use function json_encode;
  18. use function min;
  19. use function strlen;
  20. /**
  21. * Handles creation of the chart.
  22. */
  23. class ChartController extends AbstractController
  24. {
  25. /** @var DatabaseInterface */
  26. private $dbi;
  27. /**
  28. * @param Response $response
  29. * @param string $db Database name.
  30. * @param string $table Table name.
  31. * @param DatabaseInterface $dbi
  32. */
  33. public function __construct($response, Template $template, $db, $table, $dbi)
  34. {
  35. parent::__construct($response, $template, $db, $table);
  36. $this->dbi = $dbi;
  37. }
  38. public function index(): void
  39. {
  40. global $db, $table, $cfg, $sql_query, $err_url;
  41. if (isset($_REQUEST['pos'], $_REQUEST['session_max_rows']) && $this->response->isAjax()
  42. ) {
  43. $this->ajax();
  44. return;
  45. }
  46. // Throw error if no sql query is set
  47. if (! isset($sql_query) || $sql_query == '') {
  48. $this->response->setRequestStatus(false);
  49. $this->response->addHTML(
  50. Message::error(__('No SQL query was set to fetch data.'))
  51. );
  52. return;
  53. }
  54. $this->addScriptFiles([
  55. 'chart.js',
  56. 'table/chart.js',
  57. 'vendor/jqplot/jquery.jqplot.js',
  58. 'vendor/jqplot/plugins/jqplot.barRenderer.js',
  59. 'vendor/jqplot/plugins/jqplot.canvasAxisLabelRenderer.js',
  60. 'vendor/jqplot/plugins/jqplot.canvasTextRenderer.js',
  61. 'vendor/jqplot/plugins/jqplot.categoryAxisRenderer.js',
  62. 'vendor/jqplot/plugins/jqplot.dateAxisRenderer.js',
  63. 'vendor/jqplot/plugins/jqplot.pointLabels.js',
  64. 'vendor/jqplot/plugins/jqplot.pieRenderer.js',
  65. 'vendor/jqplot/plugins/jqplot.enhancedPieLegendRenderer.js',
  66. 'vendor/jqplot/plugins/jqplot.highlighter.js',
  67. ]);
  68. $url_params = [];
  69. /**
  70. * Runs common work
  71. */
  72. if (strlen($table) > 0) {
  73. Util::checkParameters(['db', 'table']);
  74. $url_params = ['db' => $db, 'table' => $table];
  75. $err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
  76. $err_url .= Url::getCommon($url_params, '&');
  77. DbTableExists::check();
  78. $url_params['goto'] = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
  79. $url_params['back'] = Url::getFromRoute('/table/sql');
  80. $this->dbi->selectDb($db);
  81. } elseif (strlen($db) > 0) {
  82. $url_params['goto'] = Util::getScriptNameForOption(
  83. $cfg['DefaultTabDatabase'],
  84. 'database'
  85. );
  86. $url_params['back'] = Url::getFromRoute('/sql');
  87. Util::checkParameters(['db']);
  88. $err_url = Util::getScriptNameForOption($cfg['DefaultTabDatabase'], 'database');
  89. $err_url .= Url::getCommon(['db' => $db], '&');
  90. if (! $this->hasDatabase()) {
  91. return;
  92. }
  93. } else {
  94. $url_params['goto'] = Util::getScriptNameForOption(
  95. $cfg['DefaultTabServer'],
  96. 'server'
  97. );
  98. $url_params['back'] = Url::getFromRoute('/sql');
  99. $err_url = Url::getFromRoute('/');
  100. if ($this->dbi->isSuperUser()) {
  101. $this->dbi->selectDb('mysql');
  102. }
  103. }
  104. $data = [];
  105. $result = $this->dbi->tryQuery($sql_query);
  106. $fields_meta = $this->dbi->getFieldsMeta($result);
  107. while ($row = $this->dbi->fetchAssoc($result)) {
  108. $data[] = $row;
  109. }
  110. $keys = array_keys($data[0]);
  111. $numeric_types = [
  112. 'int',
  113. 'real',
  114. ];
  115. $numeric_column_count = 0;
  116. foreach ($keys as $idx => $key) {
  117. if (! in_array($fields_meta[$idx]->type, $numeric_types)) {
  118. continue;
  119. }
  120. $numeric_column_count++;
  121. }
  122. if ($numeric_column_count == 0) {
  123. $this->response->setRequestStatus(false);
  124. $this->response->addJSON(
  125. 'message',
  126. __('No numeric columns present in the table to plot.')
  127. );
  128. return;
  129. }
  130. $url_params['db'] = $db;
  131. $url_params['reload'] = 1;
  132. /**
  133. * Displays the page
  134. */
  135. $this->render('table/chart/tbl_chart', [
  136. 'url_params' => $url_params,
  137. 'keys' => $keys,
  138. 'fields_meta' => $fields_meta,
  139. 'numeric_types' => $numeric_types,
  140. 'numeric_column_count' => $numeric_column_count,
  141. 'sql_query' => $sql_query,
  142. ]);
  143. }
  144. /**
  145. * Handle ajax request
  146. */
  147. public function ajax(): void
  148. {
  149. global $db, $table, $sql_query, $url_params, $err_url, $cfg;
  150. if (strlen($table) > 0 && strlen($db) > 0) {
  151. Util::checkParameters(['db', 'table']);
  152. $url_params = ['db' => $db, 'table' => $table];
  153. $err_url = Util::getScriptNameForOption($cfg['DefaultTabTable'], 'table');
  154. $err_url .= Url::getCommon($url_params, '&');
  155. DbTableExists::check();
  156. }
  157. $parser = new Parser($sql_query);
  158. /**
  159. * @var SelectStatement $statement
  160. */
  161. $statement = $parser->statements[0];
  162. if (empty($statement->limit)) {
  163. $statement->limit = new Limit(
  164. $_REQUEST['session_max_rows'],
  165. $_REQUEST['pos']
  166. );
  167. } else {
  168. $start = $statement->limit->offset + $_REQUEST['pos'];
  169. $rows = min(
  170. $_REQUEST['session_max_rows'],
  171. $statement->limit->rowCount - $_REQUEST['pos']
  172. );
  173. $statement->limit = new Limit($rows, $start);
  174. }
  175. $sql_with_limit = $statement->build();
  176. $data = [];
  177. $result = $this->dbi->tryQuery($sql_with_limit);
  178. while ($row = $this->dbi->fetchAssoc($result)) {
  179. $data[] = $row;
  180. }
  181. if (empty($data)) {
  182. $this->response->setRequestStatus(false);
  183. $this->response->addJSON('message', __('No data to display'));
  184. return;
  185. }
  186. $sanitized_data = [];
  187. foreach ($data as $data_row_number => $data_row) {
  188. $tmp_row = [];
  189. foreach ($data_row as $data_column => $data_value) {
  190. $escaped_value = $data_value === null ? null : htmlspecialchars($data_value);
  191. $tmp_row[htmlspecialchars($data_column)] = $escaped_value;
  192. }
  193. $sanitized_data[] = $tmp_row;
  194. }
  195. $this->response->setRequestStatus(true);
  196. $this->response->addJSON('message', null);
  197. $this->response->addJSON('chartData', json_encode($sanitized_data));
  198. }
  199. }