1 | <?php |
||||
2 | |||||
3 | /** |
||||
4 | * PHPPgAdmin 6.1.3 |
||||
5 | */ |
||||
6 | |||||
7 | namespace PHPPgAdmin\Controller; |
||||
8 | |||||
9 | /** |
||||
10 | * Base controller class. |
||||
11 | */ |
||||
12 | class SqlController extends BaseController |
||||
13 | { |
||||
14 | public $query = ''; |
||||
15 | |||||
16 | public $subject = ''; |
||||
17 | |||||
18 | public $start_time; |
||||
19 | |||||
20 | public $duration; |
||||
21 | |||||
22 | public $controller_title = 'strqueryresults'; |
||||
23 | |||||
24 | /** |
||||
25 | * Default method to render the controller according to the action parameter. |
||||
26 | */ |
||||
27 | public function render() |
||||
28 | { |
||||
29 | $data = $this->misc->getDatabaseAccessor(); |
||||
30 | |||||
31 | \set_time_limit(0); |
||||
32 | |||||
33 | // We need to store the query in a session for editing purposes |
||||
34 | // We avoid GPC vars to avoid truncating long queries |
||||
35 | if (isset($_REQUEST['subject']) && 'history' === $_REQUEST['subject']) { |
||||
36 | // Or maybe we came from the history popup |
||||
37 | $_SESSION['sqlquery'] = $_SESSION['history'][$_REQUEST['server']][$_REQUEST['database']][$_GET['queryid']]['query']; |
||||
38 | $this->query = $_SESSION['sqlquery']; |
||||
39 | } elseif (isset($_POST['query'])) { |
||||
40 | // Or maybe we came from an sql form |
||||
41 | $_SESSION['sqlquery'] = $_POST['query']; |
||||
42 | $this->query = $_SESSION['sqlquery']; |
||||
43 | } else { |
||||
44 | echo 'could not find the query!!'; |
||||
45 | } |
||||
46 | |||||
47 | // Pagination maybe set by a get link that has it as FALSE, |
||||
48 | // if that's the case, unset the variable. |
||||
49 | if (isset($_REQUEST['paginate']) && 'f' === $_REQUEST['paginate']) { |
||||
50 | unset($_REQUEST['paginate'], $_POST['paginate'], $_GET['paginate']); |
||||
51 | } |
||||
52 | |||||
53 | if (isset($_REQUEST['subject'])) { |
||||
54 | $this->subject = $_REQUEST['subject']; |
||||
55 | } |
||||
56 | |||||
57 | // Check to see if pagination has been specified. In that case, send to display |
||||
58 | // script for pagination |
||||
59 | // if a file is given or the request is an explain, do not paginate |
||||
60 | if (isset($_REQUEST['paginate']) && |
||||
61 | !(isset($_FILES['script']) && 0 < $_FILES['script']['size']) && |
||||
62 | (0 === \preg_match('/^\s*explain/i', $this->query))) { |
||||
63 | //if (!(isset($_FILES['script']) && $_FILES['script']['size'] > 0)) { |
||||
64 | |||||
65 | $display_controller = new DisplayController($this->getContainer()); |
||||
66 | |||||
67 | return $display_controller->render(); |
||||
68 | } |
||||
69 | |||||
70 | $this->printHeader($this->headerTitle(), null, true, 'header_sqledit.twig'); |
||||
71 | $this->printBody(); |
||||
72 | $this->printTrail('database'); |
||||
73 | $this->printTitle($this->lang['strqueryresults']); |
||||
74 | |||||
75 | // Set the schema search path |
||||
76 | if (isset($_REQUEST['search_path'])) { |
||||
77 | if (0 !== $data->setSearchPath(\array_map('trim', \explode(',', $_REQUEST['search_path'])))) { |
||||
78 | return $this->printFooter(); |
||||
79 | } |
||||
80 | } |
||||
81 | |||||
82 | // May as well try to time the query |
||||
83 | if (\function_exists('microtime')) { |
||||
84 | [$usec, $sec] = \explode(' ', \microtime()); |
||||
85 | $this->start_time = ((float) $usec + (float) $sec); |
||||
86 | } |
||||
87 | |||||
88 | $rs = $this->doDefault(); |
||||
89 | |||||
90 | $this->doFooter(true, 'footer_sqledit.twig', $rs); |
||||
91 | } |
||||
92 | |||||
93 | public function doDefault() |
||||
94 | { |
||||
95 | $_connection = $this->misc->getConnection(); |
||||
96 | |||||
97 | try { |
||||
98 | // Execute the query. If it's a script upload, special handling is necessary |
||||
99 | if (isset($_FILES['script']) && 0 < $_FILES['script']['size']) { |
||||
100 | return $this->execute_script(); |
||||
101 | } |
||||
102 | |||||
103 | return $this->execute_query(); |
||||
104 | } catch (\PHPPgAdmin\ADOdbException $e) { |
||||
105 | $message = $e->getMessage(); |
||||
0 ignored issues
–
show
Unused Code
introduced
by
![]() |
|||||
106 | $trace = $e->getTraceAsString(); |
||||
0 ignored issues
–
show
|
|||||
107 | $lastError = $_connection->getLastError(); |
||||
0 ignored issues
–
show
|
|||||
108 | |||||
109 | return null; |
||||
110 | } |
||||
111 | } |
||||
112 | |||||
113 | private function execute_script() |
||||
114 | { |
||||
115 | $misc = $this->misc; |
||||
116 | $data = $this->misc->getDatabaseAccessor(); |
||||
117 | $_connection = $this->misc->getConnection(); |
||||
118 | $lang = $this->lang; |
||||
119 | /** |
||||
120 | * This is a callback function to display the result of each separate query. |
||||
121 | * |
||||
122 | * @param ADORecordSet $rs The recordset returned by the script execetor |
||||
123 | */ |
||||
124 | $sqlCallback = static function ($query, $rs, $lineno) use ($data, $misc, $lang, $_connection): void { |
||||
125 | // Check if $rs is false, if so then there was a fatal error |
||||
126 | if (false === $rs) { |
||||
127 | echo \htmlspecialchars($_FILES['script']['name']), ':', $lineno, ': ', \nl2br(\htmlspecialchars($_connection->getLastError())), '<br/>' . \PHP_EOL; |
||||
128 | } else { |
||||
129 | // Print query results |
||||
130 | switch (\pg_result_status($rs)) { |
||||
131 | case \PGSQL_TUPLES_OK: |
||||
132 | // If rows returned, then display the results |
||||
133 | $num_fields = \pg_numfields($rs); |
||||
134 | echo "<p><table>\n<tr>"; |
||||
135 | |||||
136 | for ($k = 0; $k < $num_fields; ++$k) { |
||||
137 | echo '<th class="data">', $misc->printVal(\pg_fieldname($rs, $k)), '</th>'; |
||||
138 | } |
||||
139 | |||||
140 | $i = 0; |
||||
141 | $row = \pg_fetch_row($rs); |
||||
142 | |||||
143 | while (false !== $row) { |
||||
144 | $id = (0 === ($i % 2) ? '1' : '2'); |
||||
145 | echo "<tr class=\"data{$id}\">" . \PHP_EOL; |
||||
146 | |||||
147 | foreach ($row as $k => $v) { |
||||
148 | echo '<td style="white-space:nowrap;">', $misc->printVal($v, \pg_fieldtype($rs, $k), ['null' => true]), '</td>'; |
||||
149 | } |
||||
150 | echo '</tr>' . \PHP_EOL; |
||||
151 | $row = \pg_fetch_row($rs); |
||||
152 | ++$i; |
||||
153 | } |
||||
154 | |||||
155 | echo '</table><br/>' . \PHP_EOL; |
||||
156 | echo $i, " {$lang['strrows']}</p>" . \PHP_EOL; |
||||
157 | |||||
158 | break; |
||||
159 | case \PGSQL_COMMAND_OK: |
||||
160 | // If we have the command completion tag |
||||
161 | if (\version_compare(\PHP_VERSION, '4.3', '>=')) { |
||||
162 | echo \htmlspecialchars(\pg_result_status($rs, \PGSQL_STATUS_STRING)), '<br/>' . \PHP_EOL; |
||||
163 | } elseif (0 < $data->conn->Affected_Rows()) { |
||||
164 | // Otherwise if any rows have been affected |
||||
165 | echo $data->conn->Affected_Rows(), " {$lang['strrowsaff']}<br/>" . \PHP_EOL; |
||||
0 ignored issues
–
show
Are you sure
$data->conn->Affected_Rows() of type false can be used in echo ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
166 | } |
||||
167 | // Otherwise output nothing... |
||||
168 | break; |
||||
169 | case \PGSQL_EMPTY_QUERY: |
||||
170 | break; |
||||
171 | |||||
172 | default: |
||||
173 | break; |
||||
174 | } |
||||
175 | } |
||||
176 | }; |
||||
177 | |||||
178 | return $data->executeScript('script', $sqlCallback); |
||||
179 | } |
||||
180 | |||||
181 | private function execute_query() |
||||
182 | { |
||||
183 | $data = $this->misc->getDatabaseAccessor(); |
||||
184 | |||||
185 | // Set fetch mode to NUM so that duplicate field names are properly returned |
||||
186 | $data->conn->setFetchMode(\ADODB_FETCH_NUM); |
||||
0 ignored issues
–
show
ADODB_FETCH_NUM of type integer is incompatible with the type The expected by parameter $mode of ADOConnection::SetFetchMode() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
187 | \set_time_limit(25000); |
||||
188 | |||||
189 | /** |
||||
190 | * @var \ADORecordSet |
||||
191 | */ |
||||
192 | $rs = $data->conn->Execute($this->query); |
||||
0 ignored issues
–
show
$this->query of type string is incompatible with the type SQL expected by parameter $sql of ADOConnection::Execute() .
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
193 | |||||
194 | echo '<form method="post" id="sqlform" action="' . $_SERVER['REQUEST_URI'] . '">'; |
||||
195 | echo '<textarea width="90%" name="query" id="query" rows="5" cols="100" resizable="true">'; |
||||
196 | |||||
197 | echo \htmlspecialchars($this->query); |
||||
198 | echo '</textarea><br>'; |
||||
199 | echo $this->view->setForm(); |
||||
200 | echo '<input type="submit"/></form>'; |
||||
201 | |||||
202 | // $rs will only be an object if there is no error |
||||
203 | if (\is_object($rs)) { |
||||
204 | // Request was run, saving it in history |
||||
205 | if (!isset($_REQUEST['nohistory'])) { |
||||
206 | $this->misc->saveScriptHistory($this->query); |
||||
207 | } |
||||
208 | |||||
209 | // Now, depending on what happened do various things |
||||
210 | |||||
211 | // First, if rows returned, then display the results |
||||
212 | if (0 < $rs->recordCount()) { |
||||
213 | echo "<table>\n<tr>"; |
||||
214 | |||||
215 | foreach ($rs->fields as $k => $v) { |
||||
216 | $finfo = $rs->fetchField($k); |
||||
217 | echo '<th class="data">', $this->misc->printVal($finfo->name), '</th>'; |
||||
218 | } |
||||
219 | echo '</tr>' . \PHP_EOL; |
||||
220 | $i = 0; |
||||
221 | |||||
222 | while (!$rs->EOF) { |
||||
223 | $id = (0 === ($i % 2) ? '1' : '2'); |
||||
224 | echo "<tr class=\"data{$id}\">" . \PHP_EOL; |
||||
225 | |||||
226 | foreach ($rs->fields as $k => $v) { |
||||
227 | $finfo = $rs->fetchField($k); |
||||
228 | echo '<td style="white-space:nowrap;">', $this->misc->printVal($v, $finfo->type, ['null' => true]), '</td>'; |
||||
229 | } |
||||
230 | echo '</tr>' . \PHP_EOL; |
||||
231 | $rs->moveNext(); |
||||
232 | ++$i; |
||||
233 | } |
||||
234 | echo '</table>' . \PHP_EOL; |
||||
235 | echo '<p>', $rs->recordCount(), " {$this->lang['strrows']}</p>" . \PHP_EOL; |
||||
236 | } elseif (0 < $data->conn->Affected_Rows()) { |
||||
237 | // Otherwise if any rows have been affected |
||||
238 | echo '<p>', $data->conn->Affected_Rows(), " {$this->lang['strrowsaff']}</p>" . \PHP_EOL; |
||||
0 ignored issues
–
show
Are you sure
$data->conn->Affected_Rows() of type false can be used in echo ?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
![]() |
|||||
239 | } else { |
||||
240 | // Otherwise nodata to print |
||||
241 | echo '<p>', $this->lang['strnodata'], '</p>' . \PHP_EOL; |
||||
242 | } |
||||
243 | |||||
244 | return $rs; |
||||
245 | } |
||||
246 | } |
||||
247 | |||||
248 | /** |
||||
249 | * @param true $doBody |
||||
250 | * @param string $template |
||||
251 | * @param null|mixed $rs |
||||
252 | */ |
||||
253 | private function doFooter(bool $doBody = true, string $template = 'footer.twig', $rs = null) |
||||
254 | { |
||||
255 | $data = $this->misc->getDatabaseAccessor(); |
||||
256 | |||||
257 | // May as well try to time the query |
||||
258 | if (null !== $this->start_time) { |
||||
259 | [$usec, $sec] = \explode(' ', \microtime()); |
||||
260 | $end_time = ((float) $usec + (float) $sec); |
||||
261 | // Get duration in milliseconds, round to 3dp's |
||||
262 | $this->duration = \number_format(($end_time - $this->start_time) * 1000, 3); |
||||
263 | } |
||||
264 | |||||
265 | // Reload the browser as we may have made schema changes |
||||
266 | $this->view->setReloadBrowser(true); |
||||
267 | |||||
268 | // Display duration if we know it |
||||
269 | if (null !== $this->duration) { |
||||
270 | echo '<p>', \sprintf($this->lang['strruntime'], $this->duration), '</p>' . \PHP_EOL; |
||||
271 | } |
||||
272 | |||||
273 | echo "<p>{$this->lang['strsqlexecuted']}</p>" . \PHP_EOL; |
||||
274 | |||||
275 | $navlinks = []; |
||||
276 | $fields = [ |
||||
277 | 'server' => $_REQUEST['server'], |
||||
278 | 'database' => $_REQUEST['database'], |
||||
279 | ]; |
||||
280 | |||||
281 | if (isset($_REQUEST['schema'])) { |
||||
282 | $fields['schema'] = $_REQUEST['schema']; |
||||
283 | } |
||||
284 | |||||
285 | // Return |
||||
286 | if (isset($_REQUEST['return'])) { |
||||
287 | $urlvars = $this->misc->getSubjectParams($_REQUEST['return']); |
||||
288 | $navlinks['back'] = [ |
||||
289 | 'attr' => [ |
||||
290 | 'href' => [ |
||||
291 | 'url' => $urlvars['url'], |
||||
292 | 'urlvars' => $urlvars['params'], |
||||
293 | ], |
||||
294 | ], |
||||
295 | 'content' => $this->lang['strback'], |
||||
296 | ]; |
||||
297 | } |
||||
298 | |||||
299 | // Edit |
||||
300 | $navlinks['alter'] = [ |
||||
301 | 'attr' => [ |
||||
302 | 'href' => [ |
||||
303 | 'url' => 'database', |
||||
304 | 'urlvars' => \array_merge($fields, [ |
||||
305 | 'action' => 'sql', |
||||
306 | ]), |
||||
307 | ], |
||||
308 | ], |
||||
309 | 'content' => $this->lang['streditsql'], |
||||
310 | ]; |
||||
311 | |||||
312 | // Create view and download |
||||
313 | if ('' !== $this->query && isset($rs) && \is_object($rs) && 0 < $rs->recordCount()) { |
||||
314 | // Report views don't set a schema, so we need to disable create view in that case |
||||
315 | if (isset($_REQUEST['schema'])) { |
||||
316 | $navlinks['createview'] = [ |
||||
317 | 'attr' => [ |
||||
318 | 'href' => [ |
||||
319 | 'url' => 'views', |
||||
320 | 'urlvars' => \array_merge($fields, [ |
||||
321 | 'action' => 'create', |
||||
322 | ]), |
||||
323 | ], |
||||
324 | ], |
||||
325 | 'content' => $this->lang['strcreateview'], |
||||
326 | ]; |
||||
327 | } |
||||
328 | |||||
329 | if (isset($_REQUEST['search_path'])) { |
||||
330 | $fields['search_path'] = $_REQUEST['search_path']; |
||||
331 | } |
||||
332 | |||||
333 | $navlinks['download'] = [ |
||||
334 | 'attr' => [ |
||||
335 | 'href' => [ |
||||
336 | 'url' => 'dataexport', |
||||
337 | 'urlvars' => $fields, |
||||
338 | ], |
||||
339 | ], |
||||
340 | 'content' => $this->lang['strdownload'], |
||||
341 | ]; |
||||
342 | } |
||||
343 | |||||
344 | $this->printNavLinks($navlinks, 'sql-form', \get_defined_vars()); |
||||
345 | |||||
346 | return $this->printFooter($doBody, $template); |
||||
347 | } |
||||
348 | } |
||||
349 |