From 5a2474b7dcf869eb74fbbd5d877a1c6ce7f3a15c Mon Sep 17 00:00:00 2001 From: Peter Sistrom Date: Tue, 12 Nov 2024 15:52:21 +1100 Subject: [PATCH] Issue #94: Expose db replace to UI --- classes/form/replace.php | 70 ++++++++++++++++++++++++++++ classes/helper.php | 80 ++++++++++++++++++++++++++++++++ cli/replace.php | 61 +----------------------- db_replace.php | 54 +++++++++++++++++++++ db_search.php | 5 ++ lang/en/tool_advancedreplace.php | 4 ++ 6 files changed, 214 insertions(+), 60 deletions(-) create mode 100644 classes/form/replace.php diff --git a/classes/form/replace.php b/classes/form/replace.php new file mode 100644 index 0000000..0faebad --- /dev/null +++ b/classes/form/replace.php @@ -0,0 +1,70 @@ +. + +/** + * Advanced site wide search-replace form. + * + * @package tool_advancedreplace + * @copyright 2024 Catalyst IT Australia Pty Ltd + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ + +namespace tool_advancedreplace\form; + +use moodleform; + +defined('MOODLE_INTERNAL') || die(); + +require_once("$CFG->libdir/formslib.php"); + +/** + * Site wide search-replace form. + */ +class replace extends moodleform { + + /** + * Form definition + * + * @return void + */ + public function definition(): void { + global $CFG, $DB; + + $mform = $this->_form; + $textareasize = ['rows' => 3, 'cols' => 50]; + $fullwidth = ['style' => 'width: 100%']; + + $mform->addElement('hidden', 'userid'); + $mform->setType('userid', PARAM_INT); + $mform->setConstant('userid', $this->_customdata['userid']); + + $mform->addElement('hidden', 'origin'); + $mform->setType('origin', PARAM_TEXT); + $mform->setConstant('origin', 'web'); + + $mform->addElement('text', 'name', get_string('field_name', 'tool_advancedreplace'), $fullwidth); + $mform->setType('name', PARAM_RAW); + $mform->setDefault('name', ''); + $mform->addElement('static', 'name_help', '', get_string("field_name_help", "tool_advancedreplace")); + + // File upload. + $mform->addElement('filepicker', 'csvfile', get_string('selectfile', 'tool_advancedreplace'), null, + ['accepted_types' => ['.csv']]); + + $this->add_action_buttons(true, get_string('replace', 'tool_advancedreplace')); + + } +} diff --git a/classes/helper.php b/classes/helper.php index a0e849a..e905a48 100644 --- a/classes/helper.php +++ b/classes/helper.php @@ -22,7 +22,9 @@ use core\exception\moodle_exception; use core_text; +use csv_import_reader; use database_column_info; +use progress_bar; use tool_advancedreplace\db_search; /** @@ -681,4 +683,82 @@ public static function read_last_line(string $filename) { } return [$lastline, $linecount]; } + + /** + * Takes csv data and replaces all matching strings within the DB + * @param string $data CSV data to be read. + */ + public static function handle_replace_csv(string $data) { + // 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) { + if (CLI_SCRIPT) { + cli_error(get_string('errorinvalidfile', 'tool_advancedreplace')); + } else { + throw new \moodle_exception(get_string('errorinvalidfile', 'tool_advancedreplace')); + } + } + + // Read the header. + $header = $csvimport->get_columns(); + if (empty($header)) { + if (CLI_SCRIPT) { + cli_error(get_string('errorinvalidfile', 'tool_advancedreplace')); + } else { + throw new \moodle_exception(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)) { + if (CLI_SCRIPT) { + cli_error(get_string('errormissingfields', 'tool_advancedreplace', implode(', ', $missingcolumns))); + } else { + throw new \moodle_exception(get_string('errormissingfields', 'tool_advancedreplace', + implode(', ', $missingcolumns))); + } + } + + // Progress bar. + $progress = new progress_bar(); + $progress->create(); + + // 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); + + // Read the data and replace the strings. + $csvimport->init(); + $rowcount = 0; + $rowskip = 0; + while ($record = $csvimport->next()) { + if (empty($record[$replaceindex])) { + // Skip if 'replace' is empty. + $rowskip++; + } else { + // Replace the string. + self::replace_text_in_a_record($record[$tableindex], $record[$columnindex], + $record[$matchindex], $record[$replaceindex], $record[$idindex]); + } + + // 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(); + } } diff --git a/cli/replace.php b/cli/replace.php index 702bb4c..a780e47 100644 --- a/cli/replace.php +++ b/cli/replace.php @@ -77,64 +77,5 @@ $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. -$csvimport->init(); -$rowcount = 0; -$rowskip = 0; -while ($record = $csvimport->next()) { - if (empty($record[$replaceindex])) { - // Skip if 'replace' is empty. - $rowskip++; - } else { - // Replace the string. - helper::replace_text_in_a_record($record[$tableindex], $record[$columnindex], - $record[$matchindex], $record[$replaceindex], $record[$idindex]); - } - - // 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(); - +helper::handle_replace_csv($data); exit(0); diff --git a/db_replace.php b/db_replace.php index e69de29..8bf0a0d 100644 --- a/db_replace.php +++ b/db_replace.php @@ -0,0 +1,54 @@ +. + +/** + * Advanced search and replace strings throughout all texts in the whole database + * + * @package tool_advancedreplace + * @copyright 2024 Catalyst IT Australia Pty Ltd + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +define('NO_OUTPUT_BUFFERING', true); // Progress bar is used here. + +use tool_advancedreplace\helper; + +require_once(__DIR__ . '/../../../config.php'); +require_once($CFG->libdir . '/adminlib.php'); +require_once($CFG->dirroot . '/lib/csvlib.class.php'); + +$url = new moodle_url('/admin/tool/advancedreplace/db_replace.php'); +$PAGE->set_url($url); + +admin_externalpage_setup('tool_advancedreplace_search'); + +$redirect = new moodle_url('/admin/tool/advancedreplace/db_search.php'); + +$customdata = [ + 'userid' => $USER->id, +]; +$form = new \tool_advancedreplace\form\replace($url->out(false), $customdata); +echo $OUTPUT->header(); +if ($form->is_cancelled()) { + redirect($redirect); +} else if ($csvcontent = $form->get_file_content('csvfile')) { + helper::handle_replace_csv($csvcontent); +} else { + // Display form. + echo $OUTPUT->heading(get_string('replacepageheader', 'tool_advancedreplace')); + $form->display(); +} + +echo $OUTPUT->footer(); diff --git a/db_search.php b/db_search.php index 612c56e..bc7bf2b 100644 --- a/db_search.php +++ b/db_search.php @@ -97,7 +97,12 @@ $url->param('id', 0); $newurl = new \moodle_url($url, ['id' => 0]); $newbutton = new \single_button($newurl, get_string('newsearch', 'tool_advancedreplace'), 'GET'); + + $replaceurl = new moodle_url('/admin/tool/advancedreplace/db_replace.php'); + $replacebutton = new \single_button($replaceurl, get_string('newreplace', 'tool_advancedreplace'), 'GET'); + echo $OUTPUT->render($newbutton); + echo $OUTPUT->render($replacebutton); } echo $OUTPUT->footer(); diff --git a/lang/en/tool_advancedreplace.php b/lang/en/tool_advancedreplace.php index 798386f..9cbea0f 100644 --- a/lang/en/tool_advancedreplace.php +++ b/lang/en/tool_advancedreplace.php @@ -88,12 +88,16 @@ $string['filespageheader'] = 'Search for text in Moodle files'; $string['lastupdated'] = 'Last updated {$a} ago'; $string['newsearch'] = 'New search'; +$string['newreplace'] = 'New replace'; +$string['replacepageheader'] = 'Replace text stored in the DB'; +$string['replace'] = 'Replace'; $string['strftimedatetimemonthshort'] = '%d %b %Y, %I:%M %p'; $string['searchdeleted'] = 'The selected search was deleted.'; $string['searchpagename'] = 'Seach and Replace in the Database'; $string['searchpageheader'] = 'Search for text stored in the DB'; $string['searchqueued'] = 'Your search has been queued as an adhoc task.'; $string['searchcopy'] = 'Search options have been copied from a previous search. This will be treated as a new search.'; +$string['selectfile'] = 'Select file'; $string['settings:logduration'] = 'Search logging'; $string['settings:logduration_help'] = 'Display log information for columns with no matches that take longer than the specified duration.'; $string['settings:excludetables'] = 'Exclude tables';