Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions classes/db_search.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ class db_search extends search {
'skiptables',
'skipcolumns',
'summary',
'timemodifiedfrom',
'timemodifiedto',
];

/** How many seconds to wait before marking a search as stale. */
Expand Down Expand Up @@ -96,6 +98,14 @@ protected static function define_properties() {
'type' => PARAM_BOOL,
'default' => 0,
],
'timemodifiedfrom' => [
'type' => PARAM_INT,
'default' => 0,
],
'timemodifiedto' => [
'type' => PARAM_INT,
'default' => 0,
],
'origin' => [
'type' => PARAM_TEXT,
],
Expand Down
45 changes: 31 additions & 14 deletions classes/file_search.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,19 +109,16 @@ class file_search {
*/
const CSV_REPLACE = 12;

/**
* 10th column of csv output - the offset of the match within the file.
*
* @var int
*/
const CSV_OFFSET = 13;

/**
* 11th column of csv output - the text that was matched.
*
* @var int
*/
const CSV_MATCH = 14;
/** @var int Time modified of the course the file is associated with */
const CSV_FILETIMEMODIFIED = 13;

/** @var int The offset of the match within the file. */
const CSV_OFFSET = 14;

/** @var int The text that was matched. */
const CSV_MATCH = 15;


/** @var int Chunk size for splitting. 10MB to make border cases rare. */
const CHUNK_SIZE = 10 * 1024 * 1024;
Expand Down Expand Up @@ -181,6 +178,17 @@ public static function files(
}

[$whereclause, $params] = self::make_where_clause($criteria);
// Restrict to files modified within the time range, if set.
$timemodifiedfrom = $record->get('timemodifiedfrom');
$timemodifiedto = $record->get('timemodifiedto');
if (!empty($timemodifiedfrom)) {
$whereclause .= ' AND f.timemodified >= :timemodifiedfrom';
$params['timemodifiedfrom'] = $timemodifiedfrom;
}
if (!empty($timemodifiedto)) {
$whereclause .= ' AND f.timemodified <= :timemodifiedto';
$params['timemodifiedto'] = $timemodifiedto;
}
// If we are running a shard, then restrict the range of id.
if (!empty($startid) || !empty($endid)) {
if (empty($finalshard)) {
Expand Down Expand Up @@ -212,7 +220,7 @@ public static function files(
$stream = fopen($output, 'w');
$columnheaders = [
'fileid', 'courseid', 'shortname', 'contextid', 'component', 'filearea', 'itemid', 'filepath', 'filename',
'mimetype', 'strategy', 'internal', 'replace', 'offset', 'match',
'mimetype', 'strategy', 'internal', 'replace', 'filetimemodified', 'offset', 'match',
];
fputcsv($stream, $columnheaders, ',', '"', '\\');
}
Expand All @@ -226,7 +234,7 @@ public static function files(
$sql = "
SELECT
f.id, f.component, f.filearea, f.contextid, f.itemid, f.filename, f.filepath, f.mimetype,
c.id AS courseid, c.shortname
c.id AS courseid, c.shortname, f.timemodified
FROM {files} f
JOIN {context} ctx ON ctx.id = f.contextid
LEFT JOIN {course_modules} cm ON cm.id = ctx.instanceid AND ctx.contextlevel = 70
Expand Down Expand Up @@ -491,6 +499,14 @@ public static function search_file(object $filerecord, object $criteria, $stream
}
}

// Create an ISO 8601 date string for recording is the CSV.
if (!empty($filerecord->timemodified)) {
$dt = new \DateTime('@' . $filerecord->timemodified); // Unix timestamp, created in UTC.
$dt->setTimezone(\core_date::get_user_timezone_object()); // Switch to the user's timezone.
$dtstr = $dt->format(DATE_ATOM); // ISO 8601 (e.g. 2026-10-14T13:45:07+11:00).
} else {
$dtstr = '-';
}
$csv = [
self::CSV_FILEID => $filerecord->id,
self::CSV_COURSEID => $filerecord->courseid,
Expand All @@ -505,6 +521,7 @@ public static function search_file(object $filerecord, object $criteria, $stream
self::CSV_STRATEGY => 'plain',
self::CSV_INTERNAL => '',
self::CSV_REPLACE => '',
self::CSV_FILETIMEMODIFIED => $dtstr,
];
switch ($filerecord->mimetype) {
case 'application/zip.h5p':
Expand Down
10 changes: 10 additions & 0 deletions classes/files.php
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ class files extends search {
'zipfilenames',
'skipzipfilenames',
'shards',
'timemodifiedfrom',
'timemodifiedto',
];

/** How many seconds to wait before marking a search as stale. */
Expand Down Expand Up @@ -120,6 +122,14 @@ protected static function define_properties() {
'default' => null,
'null' => NULL_ALLOWED,
],
'timemodifiedfrom' => [
'type' => PARAM_INT,
'default' => 0,
],
'timemodifiedto' => [
'type' => PARAM_INT,
'default' => 0,
],
'origin' => [
'type' => PARAM_TEXT,
],
Expand Down
33 changes: 33 additions & 0 deletions classes/form/files.php
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,39 @@ public function definition(): void {
$mform->setDefault('shards', 1);
$mform->addElement('static', 'shards_help', '', get_string('field_shards_help', self::PLUGIN));

$mform->addElement(
'date_time_selector',
'timemodifiedfrom',
get_string('reportfilterfrom', self::PLUGIN),
['optional' => true]
);
$mform->addElement(
'date_time_selector',
'timemodifiedto',
get_string('reportfilterto', self::PLUGIN),
['optional' => true]
);

$this->add_action_buttons(true, get_string('search'));
}

/**
* Extra validation of the time filter.
*
* @param \stdClass $data Data to validate.
* @param array $files Array of files.
* @param array $errors Currently reported errors.
* @return array of additional errors.
*/
protected function extra_validation($data, $files, array &$errors) {
$newerrors = [];
if (
!empty($data->timemodifiedfrom) &&
!empty($data->timemodifiedto) &&
$data->timemodifiedfrom > $data->timemodifiedto
) {
$newerrors['timemodifiedto'] = get_string('reportfilterinvalidrange', self::PLUGIN);
}
return $newerrors;
}
}
136 changes: 136 additions & 0 deletions classes/form/results_filter_form.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
<?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/>.

namespace tool_advancedreplace\form;

defined('MOODLE_INTERNAL') || die();

require_once($CFG->libdir . '/formslib.php');

/**
* Form for filtering by date and time.
*
* @package tool_advancedreplace\form
* @copyright 2026 Catalyst IT
* @author Jason den Dulk <jasondendulk@catalyst-au.net>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class results_filter_form extends \moodleform {
/**
* Define the form.
*/
protected function definition() {
$mform = $this->_form;

$mform->addElement('hidden', 'id');
$mform->setType('id', PARAM_INT);
$mform->setConstant('id', $this->_customdata['id']);

if (isset($this->_customdata['page'])) {
$mform->addElement('hidden', 'page');
$mform->setType('page', PARAM_INT);
$mform->setConstant('page', $this->_customdata['page']);
}

if (isset($this->_customdata['perpage'])) {
$mform->addElement('hidden', 'perpage');
$mform->setType('perpage', PARAM_INT);
$mform->setConstant('perpage', $this->_customdata['perpage']);
}

$mform->addElement(
'date_time_selector',
'reportfilterfrom',
get_string('reportfilterfrom', 'tool_advancedreplace'),
['optional' => true]
);
if (isset($this->_customdata['timefrom'])) {
$mform->setDefault('reportfilterfrom', $this->_customdata['timefrom']);
}
$mform->addElement(
'date_time_selector',
'reportfilterto',
get_string('reportfilterto', 'tool_advancedreplace'),
['optional' => true]
);
if (isset($this->_customdata['timeto'])) {
$mform->setDefault('reportfilterto', $this->_customdata['timeto']);
}

$buttons = [];
$buttons[] = &$mform->createElement('submit', 'submitbutton', get_string('filter'));
if (isset($this->_customdata['clearurl'])) {
$buttons[] = &$mform->createElement(
'static',
'clearfilter',
'',
\html_writer::link($this->_customdata['clearurl'], get_string('clear'), ['class' => 'btn btn-secondary'])
);
}
$mform->addGroup($buttons, 'filterbuttons', '', ' ', false);
}

/**
* After definition hook.
*/
protected function after_definition() {
parent::after_definition();

// The parent constructor adds this after definition().
$this->_form->removeElement('sesskey');
$this->_form->disable_form_change_checker();
}

/**
* Alters submitted data to be suitable for quickforms processing.
*
* @param $method
* @throws \coding_exception
*/
public function _process_submission($method) { // phpcs:ignore PSR2.Methods.MethodDeclaration.Underscore
if ($method !== 'get') {
throw new \coding_exception('This search form must use GET.');
}

// Deliberately accept only GET data, with no files or sesskey check.
$submission = $_GET;
$marker = '_qf__' . $this->_formname;
if (($submission[$marker] ?? null) != 1) {
$submission = [];
}

$this->_form->updateSubmission($submission, []);
}

/**
* Extra validation.
*
* @param $data
* @param $files
* @return array
*/
public function validation($data, $files) {
$errors = parent::validation($data, $files);
if (
!empty($data['reportfilterfrom']) &&
!empty($data['reportfilterto']) &&
$data['reportfilterfrom'] > $data['reportfilterto']
) {
$errors['reportfilterto'] = get_string('reportfilterinvalidrange', 'tool_advancedreplace');
}
return $errors;
}
}
34 changes: 34 additions & 0 deletions classes/form/search.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,40 @@ public function definition(): void {
$mform->addElement('checkbox', 'summary', get_string('field_summary', 'tool_advancedreplace'));
$mform->addHelpButton('summary', 'field_summary', 'tool_advancedreplace');

$mform->addElement(
'date_time_selector',
'timemodifiedfrom',
get_string('reportfilterfrom', 'tool_advancedreplace'),
['optional' => true]
);
$mform->addHelpButton('timemodifiedfrom', 'field_timemodified', 'tool_advancedreplace');
$mform->addElement(
'date_time_selector',
'timemodifiedto',
get_string('reportfilterto', 'tool_advancedreplace'),
['optional' => true]
);

$this->add_action_buttons(true, get_string('search'));
}

/**
* Extra validation of the time filter.
*
* @param \stdClass $data Data to validate.
* @param array $files Array of files.
* @param array $errors Currently reported errors.
* @return array of additional errors.
*/
protected function extra_validation($data, $files, array &$errors) {
$newerrors = [];
if (
!empty($data->timemodifiedfrom) &&
!empty($data->timemodifiedto) &&
$data->timemodifiedfrom > $data->timemodifiedto
) {
$newerrors['timemodifiedto'] = get_string('reportfilterinvalidrange', 'tool_advancedreplace');
}
return $newerrors;
}
}
Loading
Loading