Skip to content

Commit

Permalink
Replace script
Browse files Browse the repository at this point in the history
  • Loading branch information
Nathan Nguyen committed Sep 25, 2024
1 parent 2d9ff1d commit 9f5a63f
Show file tree
Hide file tree
Showing 4 changed files with 250 additions and 1 deletion.
96 changes: 96 additions & 0 deletions classes/helper.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
require_once($CFG->libdir . '/adminlib.php');

use core\exception\moodle_exception;
use core_text;
use database_column_info;

/**
Expand Down Expand Up @@ -272,6 +273,7 @@ public static function plain_text_search(string $search, string $table,
$record->courseshortname ?? '',
$record->id,
$record->$columnname,
'',
]);
}
} else {
Expand Down Expand Up @@ -360,6 +362,7 @@ public static function regular_expression_search(string $search, string $table,
$record->courseshortname ?? '',
$record->id,
$match,
'',
]);
}
}
Expand All @@ -371,4 +374,97 @@ public static function regular_expression_search(string $search, string $table,
}
return $results;
}

/**
* Get column info from column name.
*
* @param string $table The table name.
* @param string $columnname The column name.
*/
private static function get_column_info(string $table, string $columnname): database_column_info {
global $DB;

$columns = $DB->get_columns($table);
$column = null;
foreach ($columns as $col) {
if ($col->name == $columnname) {
$column = $col;
break;
}
}

if (is_null($column)) {
throw new moodle_exception(get_string('errorcolumnnotfound', 'tool_advancedreplace', $columnname));
}

return $column;
}

/**
* Replace all text in a table and column.
*
* @param string $table The table to search.
* @param string $columnname The column to search.
* @param string $search The text to search for.
* @param string $replace The text to replace with.
* @param int $id The id of the record to restrict the search.
*/
public static function replace_text_in_a_record(string $table, string $columnname,
string $search, string $replace, int $id) {

$column = self::get_column_info($table, $columnname);

self::replace_all_text($table, $column, $search, $replace, ' AND id = ?', [$id]);
}

/**
* A clone of the core function replace_all_text.
* We have optional id parameter to restrict the search.
*
* @since Moodle 2.6.1
* @param string $table name of the table
* @param database_column_info $column
* @param string $search text to search for
* @param string $replace text to replace with
* @param string $wheresql additional where clause
* @param array $whereparams parameters for the where clause
*/
private static function replace_all_text($table, database_column_info $column, string $search, string $replace,
string $wheresql = '', array $whereparams = []) {
global $DB;

if (!$DB->replace_all_text_supported()) {
throw new moodle_exception(get_string('errorreplacetextnotsupported', 'tool_advancedreplace'));
}

// Enclose the column name by the proper quotes if it's a reserved word.
$columnname = $DB->get_manager()->generator->getEncQuoted($column->name);

$searchsql = $DB->sql_like($columnname, '?');
$searchparam = '%'.$DB->sql_like_escape($search).'%';

// Additional where clause.
$searchsql .= $wheresql;
$params = [$search, $replace, $searchparam] + $whereparams;

switch ($column->meta_type) {
case 'C':
if (core_text::strlen($search) < core_text::strlen($replace)) {
$colsize = $column->max_length;
$sql = "UPDATE {".$table."}
SET $columnname = " . $DB->sql_substr("REPLACE(" . $columnname . ", ?, ?)", 1, $colsize) . "
WHERE $searchsql";
break;
}
// Otherwise, do not break and use the same query as in the 'X' case.
case 'X':
$sql = "UPDATE {".$table."}
SET $columnname = REPLACE($columnname, ?, ?)
WHERE $searchsql";
break;
default:
throw new moodle_exception(get_string('errorcolumntypenotsupported', 'tool_advancedreplace'));
}
$DB->execute($sql, $params);
}
}
2 changes: 1 addition & 1 deletion cli/find.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@

// Show header.
if (!$options['summary']) {
fputcsv($fp, ['table', 'column', 'courseid', 'shortname', 'id', 'match']);
fputcsv($fp, ['table', 'column', 'courseid', 'shortname', 'id', 'match', 'replace']);
} else {
fputcsv($fp, ['table', 'column']);
}
Expand Down
147 changes: 147 additions & 0 deletions cli/replace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

/**
* Replace strings using uploaded CSV file.
*
* @package tool_advancedreplace
* @copyright 2024 Catalyst IT Australia Pty Ltd
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

use tool_advancedreplace\helper;

define('CLI_SCRIPT', true);

require(__DIR__.'/../../../../config.php');
require_once($CFG->libdir.'/clilib.php');
require_once($CFG->libdir.'/adminlib.php');
require_once($CFG->dirroot . '/lib/csvlib.class.php');
$help =
"Replace strings using uploaded CSV file..
Options:
--input=FILE Required. Input CSV file produced by find.php in detail mode.
-h, --help Print out this help.
Example:
\$ sudo -u www-data /usr/bin/php admin/tool/advancedreplace/cli/replace.php --input=/tmp/result.csv
";

list($options, $unrecognized) = cli_get_params(
[
'input' => null,
'help' => false,
],
[
'h' => 'help',
]
);
core_php_time_limit::raise();

if ($unrecognized) {
$unrecognized = implode("\n ", $unrecognized);
cli_error(get_string('cliunknowoption', 'admin', $unrecognized));
}

// Ensure that we have required parameters.
if ($options['help'] || empty($options['input'])) {
echo $help;
exit(0);
}

try {
$file = validate_param($options['input'], PARAM_PATH);
} catch (invalid_parameter_exception $e) {
cli_error(get_string('errorinvalidparam', 'tool_advancedreplace'));
}

if (!file_exists($file)) {
cli_error(get_string('errorfilenotfound', 'tool_advancedreplace'));
}

// Open the file for reading.
$fp = fopen($file, 'r');
$data = fread($fp, filesize($file));
fclose($fp);

// Load the CSV content.
$iid = csv_import_reader::get_new_iid('tool_advancedreplace');
$csvimport = new csv_import_reader($iid, 'tool_advancedreplace');
$contentcount = $csvimport->load_csv_content($data, 'utf-8', 'comma');

if ($contentcount === false) {
cli_error(get_string('errorinvalidfile', 'tool_advancedreplace'));
}

// Read the header.
$header = $csvimport->get_columns();
if (empty($header)) {
cli_error(get_string('errorinvalidfile', 'tool_advancedreplace'));
}

// Check if all required columns are present, and show which ones are missing.
$requiredcolumns = ['table', 'column', 'id', 'match', 'replace'];
$missingcolumns = array_diff($requiredcolumns, $header);

if (!empty($missingcolumns)) {
cli_error(get_string('errormissingfields', 'tool_advancedreplace', implode(', ', $missingcolumns)));
}

// Column indexes.
$tableindex = array_search('table', $header);
$columnindex = array_search('column', $header);
$idindex = array_search('id', $header);
$matchindex = array_search('match', $header);
$replaceindex = array_search('replace', $header);

// Progress bar.
$progress = new progress_bar();
$progress->create();

// Read the data and replace the strings.
$dataset = [];
$csvimport->init();
$rowcount = 0;
$rowskip = 0;
while ($record = $csvimport->next()) {
$dataset[] = $record;
$table = $record[$tableindex];
$columnname = $record[$columnindex];
$id = $record[$idindex];
$match = $record[$matchindex];
$replace = $record[$replaceindex];

if (empty($replace)) {
// Skip if 'replace' is empty.
$rowskip++;
} else {
// Replace the string.
helper::replace_text_in_a_record($table, $columnname, $match, $replace, $id);
}

// Update the progress bar.
$rowcount++;
$progress->update_full(100 * $rowcount / $contentcount, "Processed $rowcount records. Skipped $rowskip records.");
}

// Show progress.
$progress->update_full('100', "Processed $rowcount records. Skipped $rowskip records.");

$csvimport->cleanup();
$csvimport->close();

exit(0);
6 changes: 6 additions & 0 deletions lang/en/tool_advancedreplace.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,14 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/

$string['errorcolumnnotfound'] = 'Column not found.';
$string['errorcolumntypenotsupported'] = 'Column type is not supported.';
$string['errorfilenotfound'] = 'File not found.';
$string['errorinvalidfile'] = 'The file is not valid.';
$string['errorinvalidparam'] = 'Invalid parameter.';
$string['errormissingfields'] = 'The following fields are missing: {$a}';
$string['errorregexnotsupported'] = 'Regular expression searches are not supported by this database.';
$string['errorreplacetextnotsupported'] = 'Replace all text is not supported by this database.';
$string['errorsearchmethod'] = 'Please choose one of the search methods: plain text or regular expression.';
$string['pluginname'] = 'Advanced DB search and replace';
$string['privacy:metadata'] = 'The plugin does not store any personal data.';

0 comments on commit 9f5a63f

Please sign in to comment.