mirror of
https://github.com/moodle/moodle.git
synced 2025-04-21 00:12:56 +02:00
MDL-72662 reportbuilder: add relative date options to date filter.
Allow user to filter dates relative to the current date, e.g dates within the previous year, the current week or next month.
This commit is contained in:
parent
1a9bee69e6
commit
e55abd713e
@ -281,7 +281,13 @@ class task_log extends base {
|
||||
$this->get_entity_name(),
|
||||
"{$tablealias}.timestart"
|
||||
))
|
||||
->add_joins($this->get_joins());
|
||||
->add_joins($this->get_joins())
|
||||
->set_limited_operators([
|
||||
date::DATE_ANY,
|
||||
date::DATE_RANGE,
|
||||
date::DATE_PREVIOUS,
|
||||
date::DATE_CURRENT,
|
||||
]);
|
||||
|
||||
// Duration filter.
|
||||
$filters[] = (new filter(
|
||||
|
@ -34,8 +34,15 @@ $string['errorreportaccess'] = 'You can not view this report';
|
||||
$string['errorsourceinvalid'] = 'Could not find valid report source';
|
||||
$string['errorsourceunavailable'] = 'Report source is not available';
|
||||
$string['filtercontains'] = 'Contains';
|
||||
$string['filterdatecurrent'] = 'Current';
|
||||
$string['filterdatedays'] = 'Day(s)';
|
||||
$string['filterdatefrom'] = 'Date from';
|
||||
$string['filterdatemonths'] = 'Month(s)';
|
||||
$string['filterdatenext'] = 'Next';
|
||||
$string['filterdateprevious'] = 'Previous';
|
||||
$string['filterdateto'] = 'Date to';
|
||||
$string['filterdateweeks'] = 'Week(s)';
|
||||
$string['filterdateyears'] = 'Year(s)';
|
||||
$string['filterdoesnotcontain'] = 'Does not contain';
|
||||
$string['filterdurationunit'] = '{$a} unit';
|
||||
$string['filterendswith'] = 'Ends with';
|
||||
|
@ -154,8 +154,6 @@ class config_change extends base {
|
||||
* @return filter[]
|
||||
*/
|
||||
protected function get_all_filters(): array {
|
||||
global $DB;
|
||||
|
||||
$tablealias = $this->get_table_alias('config_log');
|
||||
|
||||
// Time modified filter.
|
||||
@ -170,6 +168,8 @@ class config_change extends base {
|
||||
->set_limited_operators([
|
||||
date::DATE_ANY,
|
||||
date::DATE_RANGE,
|
||||
date::DATE_PREVIOUS,
|
||||
date::DATE_CURRENT,
|
||||
]);
|
||||
|
||||
// Setting filter.
|
||||
|
@ -18,6 +18,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace core_reportbuilder\local\filters;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use lang_string;
|
||||
use MoodleQuickForm;
|
||||
use core_reportbuilder\local\helpers\database;
|
||||
@ -45,6 +46,27 @@ class date extends base {
|
||||
/** @var int Date within defined range */
|
||||
public const DATE_RANGE = 3;
|
||||
|
||||
/** @var int Date in the previous [X relative date unit(s)] */
|
||||
public const DATE_PREVIOUS = 4;
|
||||
|
||||
/** @var int Date in current [relative date unit] */
|
||||
public const DATE_CURRENT = 5;
|
||||
|
||||
/** @var int Date in the next [X relative date unit(s)] */
|
||||
public const DATE_NEXT = 6;
|
||||
|
||||
/** @var int Relative date unit for a day */
|
||||
public const DATE_UNIT_DAY = 1;
|
||||
|
||||
/** @var int Relative date unit for a week */
|
||||
public const DATE_UNIT_WEEK = 2;
|
||||
|
||||
/** @var int Relative date unit for a month */
|
||||
public const DATE_UNIT_MONTH = 3;
|
||||
|
||||
/** @var int Relative date unit for a month */
|
||||
public const DATE_UNIT_YEAR = 4;
|
||||
|
||||
/**
|
||||
* Return an array of operators available for this filter
|
||||
*
|
||||
@ -56,6 +78,9 @@ class date extends base {
|
||||
self::DATE_NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'),
|
||||
self::DATE_EMPTY => new lang_string('filterisempty', 'core_reportbuilder'),
|
||||
self::DATE_RANGE => new lang_string('filterrange', 'core_reportbuilder'),
|
||||
self::DATE_PREVIOUS => new lang_string('filterdateprevious', 'core_reportbuilder'),
|
||||
self::DATE_CURRENT => new lang_string('filterdatecurrent', 'core_reportbuilder'),
|
||||
self::DATE_NEXT => new lang_string('filterdatenext', 'core_reportbuilder'),
|
||||
];
|
||||
|
||||
return $this->filter->restrict_limited_operators($operators);
|
||||
@ -67,11 +92,46 @@ class date extends base {
|
||||
* @param MoodleQuickForm $mform
|
||||
*/
|
||||
public function setup_form(MoodleQuickForm $mform): void {
|
||||
// Operator selector.
|
||||
$operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header());
|
||||
$mform->addElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators())->setHiddenLabel(true);
|
||||
|
||||
$elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators());
|
||||
$mform->setType("{$this->name}_operator", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_operator", self::DATE_ANY);
|
||||
|
||||
// Value selector for previous and next operators.
|
||||
$valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header());
|
||||
|
||||
$elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]);
|
||||
$mform->setType("{$this->name}_value", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_value", 1);
|
||||
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_ANY);
|
||||
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_NOT_EMPTY);
|
||||
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_EMPTY);
|
||||
$mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_RANGE);
|
||||
$mform->disabledIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_CURRENT);
|
||||
|
||||
// Unit selector for previous and next operators.
|
||||
$unitlabel = get_string('filterdurationunit', 'core_reportbuilder', $this->get_header());
|
||||
$units = [
|
||||
self::DATE_UNIT_DAY => get_string('filterdatedays', 'core_reportbuilder'),
|
||||
self::DATE_UNIT_WEEK => get_string('filterdateweeks', 'core_reportbuilder'),
|
||||
self::DATE_UNIT_MONTH => get_string('filterdatemonths', 'core_reportbuilder'),
|
||||
self::DATE_UNIT_YEAR => get_string('filterdateyears', 'core_reportbuilder'),
|
||||
];
|
||||
|
||||
$elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units);
|
||||
$mform->setType("{$this->name}_unit", PARAM_INT);
|
||||
$mform->setDefault("{$this->name}_unit", self::DATE_UNIT_DAY);
|
||||
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_ANY);
|
||||
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_NOT_EMPTY);
|
||||
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_EMPTY);
|
||||
$mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_RANGE);
|
||||
|
||||
// Add operator/value/unit group.
|
||||
$mform->addGroup($elements, "{$this->name}_group", '', null, false);
|
||||
|
||||
// Date selectors for range operator.
|
||||
$mform->addElement('date_selector', "{$this->name}_from", get_string('filterdatefrom', 'core_reportbuilder'),
|
||||
['optional' => true]);
|
||||
$mform->setType("{$this->name}_from", PARAM_INT);
|
||||
@ -95,7 +155,10 @@ class date extends base {
|
||||
$fieldsql = $this->filter->get_field_sql();
|
||||
$params = $this->filter->get_field_params();
|
||||
|
||||
$operator = $values["{$this->name}_operator"] ?? self::DATE_ANY;
|
||||
$operator = (int) ($values["{$this->name}_operator"] ?? self::DATE_ANY);
|
||||
$dateunitvalue = (int) ($values["{$this->name}_value"] ?? 1);
|
||||
$dateunit = (int) ($values["{$this->name}_unit"] ?? self::DATE_UNIT_DAY);
|
||||
|
||||
switch ($operator) {
|
||||
case self::DATE_NOT_EMPTY:
|
||||
$sql = "{$fieldsql} IS NOT NULL AND {$fieldsql} <> 0";
|
||||
@ -122,6 +185,26 @@ class date extends base {
|
||||
|
||||
$sql = implode(' AND ', $clauses);
|
||||
|
||||
break;
|
||||
// Relative helper method can handle these three cases.
|
||||
case self::DATE_PREVIOUS:
|
||||
case self::DATE_CURRENT:
|
||||
case self::DATE_NEXT:
|
||||
|
||||
// Previous and next operators require a unit value greater than zero.
|
||||
if ($operator !== self::DATE_CURRENT && $dateunitvalue === 0) {
|
||||
return ['', []];
|
||||
}
|
||||
|
||||
$paramdatefrom = database::generate_param_name();
|
||||
$paramdateto = database::generate_param_name();
|
||||
|
||||
$sql = "{$fieldsql} >= :{$paramdatefrom} AND {$fieldsql} <= :{$paramdateto}";
|
||||
[
|
||||
$params[$paramdatefrom],
|
||||
$params[$paramdateto],
|
||||
] = self::get_relative_timeframe($operator, $dateunitvalue, $dateunit);
|
||||
|
||||
break;
|
||||
default:
|
||||
// Invalid or inactive filter.
|
||||
@ -130,4 +213,84 @@ class date extends base {
|
||||
|
||||
return [$sql, $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return start and end time of given relative date period
|
||||
*
|
||||
* @param int $operator
|
||||
* @param int $dateunitvalue
|
||||
* @param int $dateunit
|
||||
* @return int[]
|
||||
*/
|
||||
private static function get_relative_timeframe(int $operator, int $dateunitvalue, int $dateunit): array {
|
||||
$datenow = new DateTimeImmutable();
|
||||
|
||||
switch ($dateunit) {
|
||||
case self::DATE_UNIT_DAY:
|
||||
// Current day.
|
||||
$datestart = $dateend = $datenow;
|
||||
|
||||
if ($operator === self::DATE_PREVIOUS) {
|
||||
$datestart = $datestart->modify("-{$dateunitvalue} day");
|
||||
$dateend = $dateend->modify('-1 day');
|
||||
} else if ($operator === self::DATE_NEXT) {
|
||||
$datestart = $datestart->modify('+1 day');
|
||||
$dateend = $dateend->modify("+{$dateunitvalue} day");
|
||||
}
|
||||
|
||||
break;
|
||||
case self::DATE_UNIT_WEEK:
|
||||
// Current week.
|
||||
$datestart = $datenow->modify('monday this week');
|
||||
$dateend = $datenow->modify('sunday this week');
|
||||
|
||||
if ($operator === self::DATE_PREVIOUS) {
|
||||
$datestart = $datestart->modify("-{$dateunitvalue} week");
|
||||
$dateend = $dateend->modify('-1 week');
|
||||
} else if ($operator === self::DATE_NEXT) {
|
||||
$datestart = $datestart->modify('+1 week');
|
||||
$dateend = $dateend->modify("+{$dateunitvalue} week");
|
||||
}
|
||||
|
||||
break;
|
||||
case self::DATE_UNIT_MONTH:
|
||||
// Current month.
|
||||
$datestart = $datenow->modify('first day of this month');
|
||||
$dateend = $datenow->modify('last day of this month');
|
||||
|
||||
[$dateyear, $datemonth] = explode('/', $datenow->format('Y/m'));
|
||||
if ($operator === self::DATE_PREVIOUS) {
|
||||
$datestart = $datestart->setDate((int) $dateyear, $datemonth - $dateunitvalue, 1);
|
||||
$dateend = $dateend->modify('last day of last month');
|
||||
} else if ($operator === self::DATE_NEXT) {
|
||||
$datestart = $datestart->modify('first day of next month');
|
||||
$dateend = $dateend->setDate((int) $dateyear, $datemonth + $dateunitvalue, 1)
|
||||
->modify('last day of this month');
|
||||
}
|
||||
|
||||
break;
|
||||
case self::DATE_UNIT_YEAR:
|
||||
// Current year.
|
||||
$datestart = $datenow->modify('first day of january this year');
|
||||
$dateend = $datenow->modify('last day of december this year');
|
||||
|
||||
$dateyear = (int) $datenow->format('Y');
|
||||
if ($operator === self::DATE_PREVIOUS) {
|
||||
$datestart = $datestart->setDate($dateyear - $dateunitvalue, 1, 1);
|
||||
$dateend = $dateend->modify('last day of december last year');
|
||||
} else if ($operator === self::DATE_NEXT) {
|
||||
$datestart = $datestart->modify('first day of january next year');
|
||||
$dateend = $dateend->setDate($dateyear + $dateunitvalue, 12, 31);
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
return [0, 0];
|
||||
}
|
||||
|
||||
return [
|
||||
$datestart->setTime(0, 0)->getTimestamp(),
|
||||
$dateend->setTime(23, 59, 59)->getTimestamp(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ use core_reportbuilder\local\report\filter;
|
||||
* @copyright 2021 Paul Holden <paulh@moodle.com>
|
||||
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
|
||||
*/
|
||||
class date_testcase extends advanced_testcase {
|
||||
class date_test extends advanced_testcase {
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_simple}
|
||||
@ -114,4 +114,64 @@ class date_testcase extends advanced_testcase {
|
||||
$usernames = $DB->get_fieldset_select('user', 'username', $select, $params);
|
||||
$this->assertEquals([$usertwo->username], $usernames);
|
||||
}
|
||||
|
||||
/**
|
||||
* Data provider for {@see test_get_sql_filter_relative}
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sql_filter_relative_provider(): array {
|
||||
return [
|
||||
'Previous day' => [date::DATE_PREVIOUS, 1, date::DATE_UNIT_DAY, '-1 day'],
|
||||
'Previous week' => [date::DATE_PREVIOUS, 1, date::DATE_UNIT_WEEK, '-1 week'],
|
||||
'Previous month' => [date::DATE_PREVIOUS, 1, date::DATE_UNIT_MONTH, 'last day of last month'],
|
||||
'Previous year' => [date::DATE_PREVIOUS, 1, date::DATE_UNIT_YEAR, 'last day of december last year'],
|
||||
|
||||
'Current day' => [date::DATE_CURRENT, null, date::DATE_UNIT_DAY],
|
||||
'Current week' => [date::DATE_CURRENT, null, date::DATE_UNIT_WEEK],
|
||||
'Current month' => [date::DATE_CURRENT, null, date::DATE_UNIT_MONTH],
|
||||
'Current year' => [date::DATE_CURRENT, null, date::DATE_UNIT_YEAR],
|
||||
|
||||
'Next day' => [date::DATE_NEXT, 1, date::DATE_UNIT_DAY, '+1 day'],
|
||||
'Next week' => [date::DATE_NEXT, 1, date::DATE_UNIT_WEEK, '+1 week'],
|
||||
'Next month' => [date::DATE_NEXT, 1, date::DATE_UNIT_MONTH, 'first day of next month'],
|
||||
'Next year' => [date::DATE_NEXT, 1, date::DATE_UNIT_YEAR, 'first day of january next year'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Unit tests for filtering relative dates
|
||||
*
|
||||
* @param int $operator
|
||||
* @param int|null $unitvalue
|
||||
* @param int $unit
|
||||
* @param string|null $timecreated Relative time suitable for passing to {@see strtotime} (or null for current time)
|
||||
*
|
||||
* @dataProvider get_sql_filter_relative_provider
|
||||
*/
|
||||
public function test_get_sql_filter_relative(int $operator, ?int $unitvalue, int $unit, ?string $timecreated = null): void {
|
||||
global $DB;
|
||||
|
||||
$this->resetAfterTest();
|
||||
|
||||
$usertimecreated = ($timecreated !== null ? strtotime($timecreated) : time());
|
||||
$user = $this->getDataGenerator()->create_user(['timecreated' => $usertimecreated]);
|
||||
|
||||
$filter = new filter(
|
||||
date::class,
|
||||
'test',
|
||||
new lang_string('yes'),
|
||||
'testentity',
|
||||
'timecreated'
|
||||
);
|
||||
|
||||
[$select, $params] = date::create($filter)->get_sql_filter([
|
||||
$filter->get_unique_identifier() . '_operator' => $operator,
|
||||
$filter->get_unique_identifier() . '_value' => $unitvalue,
|
||||
$filter->get_unique_identifier() . '_unit' => $unit,
|
||||
]);
|
||||
|
||||
$matchingusers = $DB->get_fieldset_select('user', 'username', $select, $params);
|
||||
$this->assertContains($user->username, $matchingusers);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user