phpmyadmin/libraries/classes/Controllers/Table/FindReplaceController.php
Maurício Meneghini Fauth d0f28cef30 Refactor table pages common code
Extracts tbl_common.inc.php code to the Common::table() method and
removes the include file.

Signed-off-by: Maurício Meneghini Fauth <mauricio@fauth.dev>
2020-01-07 22:49:24 -03:00

393 lines
12 KiB
PHP

<?php
/**
* @package PhpMyAdmin\Controllers\Table
*/
declare(strict_types=1);
namespace PhpMyAdmin\Controllers\Table;
use PhpMyAdmin\Common;
use PhpMyAdmin\DatabaseInterface;
use PhpMyAdmin\Html\Generator;
use PhpMyAdmin\Response;
use PhpMyAdmin\Template;
use PhpMyAdmin\Util;
/**
* Handles find and replace tab.
*
* Displays find and replace form, allows previewing and do the replacing.
*
* @package PhpMyAdmin\Controllers\Table
*/
class FindReplaceController extends AbstractController
{
/**
* @var array
*/
private $_columnNames;
/**
* @var array
*/
private $_columnTypes;
/**
* @var string
*/
private $_connectionCharSet;
/**
* @param Response $response Response object
* @param DatabaseInterface $dbi DatabaseInterface object
* @param Template $template Template object
* @param string $db Database name
* @param string $table Table name
*/
public function __construct($response, $dbi, Template $template, $db, $table)
{
parent::__construct($response, $dbi, $template, $db, $table);
$this->_columnNames = [];
$this->_columnTypes = [];
$this->_loadTableInfo();
$this->_connectionCharSet = $this->dbi->fetchValue(
'SELECT @@character_set_connection'
);
}
/**
* @return void
*/
public function index(): void
{
Common::table();
if (isset($_POST['find'])) {
$this->findAction();
return;
}
$header = $this->response->getHeader();
$scripts = $header->getScripts();
$scripts->addFile('table/find_replace.js');
if (isset($_POST['replace'])) {
$this->replaceAction();
}
// Displays the find and replace form
$this->displaySelectionFormAction();
}
/**
* Gets all the columns of a table along with their types.
*
* @return void
*/
private function _loadTableInfo(): void
{
// Gets the list and number of columns
$columns = $this->dbi->getColumns(
$this->db,
$this->table,
null,
true
);
foreach ($columns as $row) {
// set column name
$this->_columnNames[] = $row['Field'];
$type = $row['Type'];
// reformat mysql query output
if (strncasecmp($type, 'set', 3) == 0
|| strncasecmp($type, 'enum', 4) == 0
) {
$type = str_replace(',', ', ', $type);
} else {
// strip the "BINARY" attribute, except if we find "BINARY(" because
// this would be a BINARY or VARBINARY column type
if (! preg_match('@BINARY[\(]@i', $type)) {
$type = str_ireplace('BINARY', '', $type);
}
$type = str_ireplace('ZEROFILL', '', $type);
$type = str_ireplace('UNSIGNED', '', $type);
$type = mb_strtolower($type);
}
if (empty($type)) {
$type = '&nbsp;';
}
$this->_columnTypes[] = $type;
}
}
/**
* Display selection form action
*
* @return void
*/
public function displaySelectionFormAction(): void
{
global $goto;
if (! isset($goto)) {
$goto = Util::getScriptNameForOption(
$GLOBALS['cfg']['DefaultTabTable'],
'table'
);
}
$column_names = $this->_columnNames;
$column_types = $this->_columnTypes;
$types = [];
$num_cols = count($column_names);
for ($i = 0; $i < $num_cols; $i++) {
$types[$column_names[$i]] = preg_replace(
'@\\(.*@s',
'',
$column_types[$i]
);
}
$this->response->addHTML(
$this->template->render('table/find_replace/index', [
'db' => $this->db,
'table' => $this->table,
'goto' => $goto,
'column_names' => $column_names,
'types' => $types,
'sql_types' => $this->dbi->types,
])
);
}
/**
* @return void
*/
public function findAction(): void
{
$useRegex = array_key_exists('useRegex', $_POST)
&& $_POST['useRegex'] == 'on';
$preview = $this->getReplacePreview(
$_POST['columnIndex'],
$_POST['find'],
$_POST['replaceWith'],
$useRegex,
$this->_connectionCharSet
);
$this->response->addJSON('preview', $preview);
}
/**
* @return void
*/
public function replaceAction(): void
{
$this->replace(
$_POST['columnIndex'],
$_POST['findString'],
$_POST['replaceWith'],
$_POST['useRegex'],
$this->_connectionCharSet
);
$this->response->addHTML(
Generator::getMessage(
__('Your SQL query has been executed successfully.'),
null,
'success'
)
);
}
/**
* Returns HTML for previewing strings found and their replacements
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param boolean $useRegex to use Regex replace or not
* @param string $charSet character set of the connection
*
* @return string HTML for previewing strings found and their replacements
*/
public function getReplacePreview(
$columnIndex,
$find,
$replaceWith,
$useRegex,
$charSet
) {
$column = $this->_columnNames[$columnIndex];
if ($useRegex) {
$result = $this->_getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
);
} else {
$sql_query = 'SELECT '
. Util::backquote($column) . ','
. ' REPLACE('
. Util::backquote($column) . ", '" . $find . "', '"
. $replaceWith
. "'),"
. ' COUNT(*)'
. ' FROM ' . Util::backquote($this->db)
. '.' . Util::backquote($this->table)
. ' WHERE ' . Util::backquote($column)
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
$sql_query .= ' GROUP BY ' . Util::backquote($column)
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
$result = $this->dbi->fetchResult($sql_query, 0);
}
return $this->template->render('table/find_replace/replace_preview', [
'db' => $this->db,
'table' => $this->table,
'column_index' => $columnIndex,
'find' => $find,
'replace_with' => $replaceWith,
'use_regex' => $useRegex,
'result' => $result,
]);
}
/**
* Finds and returns Regex pattern and their replacements
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param string $charSet character set of the connection
*
* @return array|bool Array containing original values, replaced values and count
*/
private function _getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
) {
$column = $this->_columnNames[$columnIndex];
$sql_query = 'SELECT '
. Util::backquote($column) . ','
. ' 1,' // to add an extra column that will have replaced value
. ' COUNT(*)'
. ' FROM ' . Util::backquote($this->db)
. '.' . Util::backquote($this->table)
. ' WHERE ' . Util::backquote($column)
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
. $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison is case sensitive
$sql_query .= ' GROUP BY ' . Util::backquote($column)
. ' ORDER BY ' . Util::backquote($column) . ' ASC';
$result = $this->dbi->fetchResult($sql_query, 0);
if (is_array($result)) {
/* Iterate over possible delimiters to get one */
$delimiters = [
'/',
'@',
'#',
'~',
'!',
'$',
'%',
'^',
'&',
'_',
];
$found = false;
for ($i = 0, $l = count($delimiters); $i < $l; $i++) {
if (strpos($find, $delimiters[$i]) === false) {
$found = true;
break;
}
}
if (! $found) {
return false;
}
$find = $delimiters[$i] . $find . $delimiters[$i];
foreach ($result as $index => $row) {
$result[$index][1] = preg_replace(
$find,
$replaceWith,
$row[0]
);
}
}
return $result;
}
/**
* Replaces a given string in a column with a give replacement
*
* @param int $columnIndex index of the column
* @param string $find string to find in the column
* @param string $replaceWith string to replace with
* @param boolean $useRegex to use Regex replace or not
* @param string $charSet character set of the connection
*
* @return void
*/
public function replace(
$columnIndex,
$find,
$replaceWith,
$useRegex,
$charSet
) {
$column = $this->_columnNames[$columnIndex];
if ($useRegex) {
$toReplace = $this->_getRegexReplaceRows(
$columnIndex,
$find,
$replaceWith,
$charSet
);
$sql_query = 'UPDATE ' . Util::backquote($this->table)
. ' SET ' . Util::backquote($column) . ' = CASE';
if (is_array($toReplace)) {
foreach ($toReplace as $row) {
$sql_query .= "\n WHEN " . Util::backquote($column)
. " = '" . $this->dbi->escapeString($row[0])
. "' THEN '" . $this->dbi->escapeString($row[1]) . "'";
}
}
$sql_query .= ' END'
. ' WHERE ' . Util::backquote($column)
. " RLIKE '" . $this->dbi->escapeString($find) . "' COLLATE "
. $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
} else {
$sql_query = 'UPDATE ' . Util::backquote($this->table)
. ' SET ' . Util::backquote($column) . ' ='
. ' REPLACE('
. Util::backquote($column) . ", '" . $find . "', '"
. $replaceWith
. "')"
. ' WHERE ' . Util::backquote($column)
. " LIKE '%" . $find . "%' COLLATE " . $charSet . '_bin'; // here we
// change the collation of the 2nd operand to a case sensitive
// binary collation to make sure that the comparison
// is case sensitive
}
$this->dbi->query(
$sql_query,
DatabaseInterface::CONNECT_USER,
DatabaseInterface::QUERY_STORE
);
$GLOBALS['sql_query'] = $sql_query;
}
}