From ac669bc9689a579976f3b8079d31e97cc5b6135d Mon Sep 17 00:00:00 2001 From: Nathan Nguyen Date: Mon, 16 Sep 2024 17:04:01 +1000 Subject: [PATCH] WIP --- admin/tool/replace/cli/find.php | 93 +++++++++++++++++++++++++++++++++ lib/adminlib.php | 46 ++++++++++++++++ lib/dml/moodle_database.php | 27 ++++++++++ 3 files changed, 166 insertions(+) create mode 100644 admin/tool/replace/cli/find.php diff --git a/admin/tool/replace/cli/find.php b/admin/tool/replace/cli/find.php new file mode 100644 index 0000000000000..e64fcfd638648 --- /dev/null +++ b/admin/tool/replace/cli/find.php @@ -0,0 +1,93 @@ +. + +/** + * Search strings throughout all texts in the whole database. + * + * @package tool_replace + * @copyright 2024 Catalyst IT Australia Pty Ltd + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +define('CLI_SCRIPT', true); + +require(__DIR__.'/../../../../config.php'); +require_once($CFG->libdir.'/clilib.php'); +require_once($CFG->libdir.'/adminlib.php'); + +$help = + "Search text throughout the whole database. + +Options: +--search=STRING String to search for. +--skiptables=STRING Skip these tables (comma separated list of tables). + The +--summary Summary mode, only shows column/table where the text is found. + If not specified, run in detail mode, which shows the full text where the search string is found. +-h, --help Print out this help. + +Example: +\$ sudo -u www-data /usr/bin/php admin/tool/replace/cli/find.php --search=thelostsoul --summary +"; + +list($options, $unrecognized) = cli_get_params( + array( + 'search' => null, + 'skiptables' => '', + 'summary' => false, + 'help' => false, + ), + array( + 'h' => 'help', + ) +); + +if ($unrecognized) { + $unrecognized = implode("\n ", $unrecognized); + cli_error(get_string('cliunknowoption', 'admin', $unrecognized)); +} + +// Ensure that we have required parameters. +if ($options['help'] || !is_string($options['search'])) { + echo $help; + exit(0); +} + +try { + $search = validate_param($options['search'], PARAM_RAW); + $skiptables = validate_param($options['skiptables'], PARAM_RAW); +} catch (invalid_parameter_exception $e) { + cli_error(get_string('invalidcharacter', 'tool_replace')); +} + +// Perform the search. +$result = db_search($search, $skiptables, $options['summary']); + +// Output the result. +foreach ($result as $table => $columns) { + foreach ($columns as $column => $rows) { + if ($options['summary']) { + echo "$table, $column\n"; + } else { + foreach ($rows as $row) { + $data = $row->$column; + echo "$table, $column, \"$data\"\n"; + } + } + } +} + +exit(0); diff --git a/lib/adminlib.php b/lib/adminlib.php index fb51e34ea3c4e..99dc7efd73e5d 100644 --- a/lib/adminlib.php +++ b/lib/adminlib.php @@ -9334,6 +9334,52 @@ function db_should_replace($table, $column = '', $additionalskiptables = ''): bo return true; } + +/** + * Search for a string in the database. + * + * @param string $search the string to search for + * @param string $additionalskiptables additional tables to skip + * @param bool $summary if true, shown only the table and column names + * @return array|bool false if no tables, or an array of results + */ +function db_search(string $search, string $additionalskiptables = '', bool $summary = false) { + global $DB; + + if (!$tables = $DB->get_tables() ) { // No tables yet at all. + return false; + } + + // If we are doing a summary, we only want to return the first match. + $limit = $summary ? 1 : 0; + + $result = []; + foreach ($tables as $table) { + // TODO: db_should_replace can be refactored/reused here. + if (!db_should_replace($table, '', $additionalskiptables)) { + continue; + } + + if ($columns = $DB->get_columns($table)) { + foreach ($columns as $column) { + if (!db_should_replace($table, $column->name)) { + continue; + } + + if ($match = $DB->search_all_text($table, $column, $search, $limit)) { + if ($summary) { + $result[$table][$column->name] = []; + } else { + $result[$table][$column->name] = $match; + } + } + } + } + } + + return $result; +} + /** * Moved from admin/replace.php so that we can use this in cron * diff --git a/lib/dml/moodle_database.php b/lib/dml/moodle_database.php index 73653196e85d8..980b3075a6c72 100644 --- a/lib/dml/moodle_database.php +++ b/lib/dml/moodle_database.php @@ -2585,6 +2585,33 @@ public function sql_intersect($selects, $fields) { return $rv; } + /** + * SQL to get the current timestamp. + * + * @param string $table The table name. + * @param database_column_info $column The column info. + * @param string $search The search string. + * @param int $limit number of records to return. + * @return array|false + */ + public function search_all_text($table, database_column_info $column, $search, $limit = 0) { + // Enclose the column name by the proper quotes if it's a reserved word. + $columnname = $this->get_manager()->generator->getEncQuoted($column->name); + + $searchsql = $this->sql_like($columnname, '?'); + $searchparam = '%'.$this->sql_like_escape($search).'%'; + + $sql = "SELECT $columnname + FROM {".$table."} + WHERE $searchsql"; + + if ($column->meta_type === 'X' || $column->meta_type === 'C') { + return $this->get_records_sql($sql, array($searchparam), 0, $limit); + } + + return false; + } + /** * Does this driver support tool_replace? *