2 declare(strict_types
=1);
4 namespace PhpMyAdmin\Controllers\Table
;
7 use PhpMyAdmin\DatabaseInterface
;
8 use PhpMyAdmin\Html\Generator
;
9 use PhpMyAdmin\Response
;
10 use PhpMyAdmin\Template
;
12 use function array_key_exists
;
14 use function is_array
;
15 use function mb_strtolower
;
16 use function preg_match
;
17 use function preg_replace
;
18 use function str_ireplace
;
19 use function str_replace
;
20 use function strncasecmp
;
24 * Handles find and replace tab.
26 * Displays find and replace form, allows previewing and do the replacing.
28 class FindReplaceController
extends AbstractController
31 private $_columnNames;
34 private $_columnTypes;
37 private $_connectionCharSet;
40 * @param Response $response Response object
41 * @param DatabaseInterface $dbi DatabaseInterface object
42 * @param Template $template Template object
43 * @param string $db Database name
44 * @param string $table Table name
46 public function __construct($response, $dbi, Template
$template, $db, $table)
48 parent
::__construct($response, $dbi, $template, $db, $table);
49 $this->_columnNames
= [];
50 $this->_columnTypes
= [];
51 $this->_loadTableInfo();
52 $this->_connectionCharSet
= $this->dbi
->fetchValue(
53 'SELECT @@character_set_connection'
57 public function index(): void
61 if (isset($_POST['find'])) {
66 $header = $this->response
->getHeader();
67 $scripts = $header->getScripts();
68 $scripts->addFile('table/find_replace.js');
70 if (isset($_POST['replace'])) {
71 $this->replaceAction();
74 // Displays the find and replace form
75 $this->displaySelectionFormAction();
79 * Gets all the columns of a table along with their types.
81 private function _loadTableInfo(): void
83 // Gets the list and number of columns
84 $columns = $this->dbi
->getColumns(
91 foreach ($columns as $row) {
93 $this->_columnNames
[] = $row['Field'];
96 // reformat mysql query output
97 if (strncasecmp($type, 'set', 3) == 0
98 ||
strncasecmp($type, 'enum', 4) == 0
100 $type = str_replace(',', ', ', $type);
102 // strip the "BINARY" attribute, except if we find "BINARY(" because
103 // this would be a BINARY or VARBINARY column type
104 if (! preg_match('@BINARY[\(]@i', $type)) {
105 $type = str_ireplace('BINARY', '', $type);
107 $type = str_ireplace('ZEROFILL', '', $type);
108 $type = str_ireplace('UNSIGNED', '', $type);
109 $type = mb_strtolower($type);
114 $this->_columnTypes
[] = $type;
119 * Display selection form action
121 public function displaySelectionFormAction(): void
125 if (! isset($goto)) {
126 $goto = Util
::getScriptNameForOption(
127 $GLOBALS['cfg']['DefaultTabTable'],
132 $column_names = $this->_columnNames
;
133 $column_types = $this->_columnTypes
;
135 $num_cols = count($column_names);
136 for ($i = 0; $i < $num_cols; $i++
) {
137 $types[$column_names[$i]] = preg_replace(
144 $this->render('table/find_replace/index', [
146 'table' => $this->table
,
148 'column_names' => $column_names,
150 'sql_types' => $this->dbi
->types
,
154 public function findAction(): void
156 $useRegex = array_key_exists('useRegex', $_POST)
157 && $_POST['useRegex'] == 'on';
159 $preview = $this->getReplacePreview(
160 $_POST['columnIndex'],
162 $_POST['replaceWith'],
164 $this->_connectionCharSet
166 $this->response
->addJSON('preview', $preview);
169 public function replaceAction(): void
172 $_POST['columnIndex'],
173 $_POST['findString'],
174 $_POST['replaceWith'],
176 $this->_connectionCharSet
178 $this->response
->addHTML(
179 Generator
::getMessage(
180 __('Your SQL query has been executed successfully.'),
188 * Returns HTML for previewing strings found and their replacements
190 * @param int $columnIndex index of the column
191 * @param string $find string to find in the column
192 * @param string $replaceWith string to replace with
193 * @param bool $useRegex to use Regex replace or not
194 * @param string $charSet character set of the connection
196 * @return string HTML for previewing strings found and their replacements
198 public function getReplacePreview(
205 $column = $this->_columnNames
[$columnIndex];
207 $result = $this->_getRegexReplaceRows(
214 $sql_query = 'SELECT '
215 . Util
::backquote($column) . ','
217 . Util
::backquote($column) . ", '" . $find . "', '"
221 . ' FROM ' . Util
::backquote($this->db
)
222 . '.' . Util
::backquote($this->table
)
223 . ' WHERE ' . Util
::backquote($column)
224 . " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
225 // change the collation of the 2nd operand to a case sensitive
226 // binary collation to make sure that the comparison
228 $sql_query .= ' GROUP BY ' . Util
::backquote($column)
229 . ' ORDER BY ' . Util
::backquote($column) . ' ASC';
231 $result = $this->dbi
->fetchResult($sql_query, 0);
234 return $this->template
->render('table/find_replace/replace_preview', [
236 'table' => $this->table
,
237 'column_index' => $columnIndex,
239 'replace_with' => $replaceWith,
240 'use_regex' => $useRegex,
246 * Finds and returns Regex pattern and their replacements
248 * @param int $columnIndex index of the column
249 * @param string $find string to find in the column
250 * @param string $replaceWith string to replace with
251 * @param string $charSet character set of the connection
253 * @return array|bool Array containing original values, replaced values and count
255 private function _getRegexReplaceRows(
261 $column = $this->_columnNames
[$columnIndex];
262 $sql_query = 'SELECT '
263 . Util
::backquote($column) . ','
264 . ' 1,' // to add an extra column that will have replaced value
266 . ' FROM ' . Util
::backquote($this->db
)
267 . '.' . Util
::backquote($this->table
)
268 . ' WHERE ' . Util
::backquote($column)
269 . " RLIKE '" . $this->dbi
->escapeString($find) . "' COLLATE "
270 . $charSet . '_bin'; // here we
271 // change the collation of the 2nd operand to a case sensitive
272 // binary collation to make sure that the comparison is case sensitive
273 $sql_query .= ' GROUP BY ' . Util
::backquote($column)
274 . ' ORDER BY ' . Util
::backquote($column) . ' ASC';
276 $result = $this->dbi
->fetchResult($sql_query, 0);
278 if (is_array($result)) {
279 /* Iterate over possible delimiters to get one */
293 for ($i = 0, $l = count($delimiters); $i < $l; $i++
) {
294 if (strpos($find, $delimiters[$i]) === false) {
302 $find = $delimiters[$i] . $find . $delimiters[$i];
303 foreach ($result as $index => $row) {
304 $result[$index][1] = preg_replace(
315 * Replaces a given string in a column with a give replacement
317 * @param int $columnIndex index of the column
318 * @param string $find string to find in the column
319 * @param string $replaceWith string to replace with
320 * @param bool $useRegex to use Regex replace or not
321 * @param string $charSet character set of the connection
325 public function replace(
332 $column = $this->_columnNames
[$columnIndex];
334 $toReplace = $this->_getRegexReplaceRows(
340 $sql_query = 'UPDATE ' . Util
::backquote($this->table
)
341 . ' SET ' . Util
::backquote($column) . ' = CASE';
342 if (is_array($toReplace)) {
343 foreach ($toReplace as $row) {
344 $sql_query .= "\n WHEN " . Util
::backquote($column)
345 . " = '" . $this->dbi
->escapeString($row[0])
346 . "' THEN '" . $this->dbi
->escapeString($row[1]) . "'";
350 . ' WHERE ' . Util
::backquote($column)
351 . " RLIKE '" . $this->dbi
->escapeString($find) . "' COLLATE "
352 . $charSet . '_bin'; // here we
353 // change the collation of the 2nd operand to a case sensitive
354 // binary collation to make sure that the comparison
357 $sql_query = 'UPDATE ' . Util
::backquote($this->table
)
358 . ' SET ' . Util
::backquote($column) . ' ='
360 . Util
::backquote($column) . ", '" . $find . "', '"
363 . ' WHERE ' . Util
::backquote($column)
364 . " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
365 // change the collation of the 2nd operand to a case sensitive
366 // binary collation to make sure that the comparison
371 DatabaseInterface
::CONNECT_USER
,
372 DatabaseInterface
::QUERY_STORE
374 $GLOBALS['sql_query'] = $sql_query;