Differences Between: [Versions 400 and 402] [Versions 401 and 402]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 declare(strict_types=1); 18 19 namespace core_reportbuilder\local\filters; 20 21 use DateTimeImmutable; 22 use lang_string; 23 use MoodleQuickForm; 24 use core_reportbuilder\local\helpers\database; 25 26 /** 27 * Date report filter 28 * 29 * This filter accepts a unix timestamp to perform date filtering on 30 * 31 * @package core_reportbuilder 32 * @copyright 2021 Paul Holden <paulh@moodle.com> 33 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 34 */ 35 class date extends base { 36 37 /** @var int Any value */ 38 public const DATE_ANY = 0; 39 40 /** @var int Non-empty (positive) value */ 41 public const DATE_NOT_EMPTY = 1; 42 43 /** @var int Empty (zero) value */ 44 public const DATE_EMPTY = 2; 45 46 /** @var int Date within defined range */ 47 public const DATE_RANGE = 3; 48 49 /** @var int Date in the last [X relative date unit(s)] */ 50 public const DATE_LAST = 4; 51 52 /** @var int Date in the previous [X relative date unit(s)] Kept for backwards compatibility */ 53 public const DATE_PREVIOUS = self::DATE_LAST; 54 55 /** @var int Date in current [relative date unit] */ 56 public const DATE_CURRENT = 5; 57 58 /** @var int Date in the next [X relative date unit(s)] */ 59 public const DATE_NEXT = 6; 60 61 /** @var int Date in the past */ 62 public const DATE_PAST = 7; 63 64 /** @var int Date in the future */ 65 public const DATE_FUTURE = 8; 66 67 /** @var int Date before [X relative date unit(s)] */ 68 public const DATE_BEFORE = 9; 69 70 /** @var int Relative date unit for an hour */ 71 public const DATE_UNIT_HOUR = 0; 72 73 /** @var int Relative date unit for a day */ 74 public const DATE_UNIT_DAY = 1; 75 76 /** @var int Relative date unit for a week */ 77 public const DATE_UNIT_WEEK = 2; 78 79 /** @var int Relative date unit for a month */ 80 public const DATE_UNIT_MONTH = 3; 81 82 /** @var int Relative date unit for a month */ 83 public const DATE_UNIT_YEAR = 4; 84 85 /** 86 * Return an array of operators available for this filter 87 * 88 * @return lang_string[] 89 */ 90 private function get_operators(): array { 91 $operators = [ 92 self::DATE_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'), 93 self::DATE_NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'), 94 self::DATE_EMPTY => new lang_string('filterisempty', 'core_reportbuilder'), 95 self::DATE_RANGE => new lang_string('filterrange', 'core_reportbuilder'), 96 self::DATE_BEFORE => new lang_string('filterdatebefore', 'core_reportbuilder'), 97 self::DATE_LAST => new lang_string('filterdatelast', 'core_reportbuilder'), 98 self::DATE_CURRENT => new lang_string('filterdatecurrent', 'core_reportbuilder'), 99 self::DATE_NEXT => new lang_string('filterdatenext', 'core_reportbuilder'), 100 self::DATE_PAST => new lang_string('filterdatepast', 'core_reportbuilder'), 101 self::DATE_FUTURE => new lang_string('filterdatefuture', 'core_reportbuilder'), 102 ]; 103 104 return $this->filter->restrict_limited_operators($operators); 105 } 106 107 /** 108 * Setup form 109 * 110 * @param MoodleQuickForm $mform 111 */ 112 public function setup_form(MoodleQuickForm $mform): void { 113 // Operator selector. 114 $operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()); 115 $typesnounit = [self::DATE_ANY, self::DATE_NOT_EMPTY, self::DATE_EMPTY, self::DATE_RANGE, 116 self::DATE_PAST, self::DATE_FUTURE]; 117 118 $elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators()); 119 $mform->setType("{$this->name}_operator", PARAM_INT); 120 $mform->setDefault("{$this->name}_operator", self::DATE_ANY); 121 122 // Value selector for last and next operators. 123 $valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()); 124 125 $elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]); 126 $mform->setType("{$this->name}_value", PARAM_INT); 127 $mform->setDefault("{$this->name}_value", 1); 128 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'in', array_merge($typesnounit, [self::DATE_CURRENT])); 129 130 // Unit selector for last and next operators. 131 $unitlabel = get_string('filterdurationunit', 'core_reportbuilder', $this->get_header()); 132 $units = [ 133 self::DATE_UNIT_HOUR => get_string('filterdatehours', 'core_reportbuilder'), 134 self::DATE_UNIT_DAY => get_string('filterdatedays', 'core_reportbuilder'), 135 self::DATE_UNIT_WEEK => get_string('filterdateweeks', 'core_reportbuilder'), 136 self::DATE_UNIT_MONTH => get_string('filterdatemonths', 'core_reportbuilder'), 137 self::DATE_UNIT_YEAR => get_string('filterdateyears', 'core_reportbuilder'), 138 ]; 139 140 $elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units); 141 $mform->setType("{$this->name}_unit", PARAM_INT); 142 $mform->setDefault("{$this->name}_unit", self::DATE_UNIT_DAY); 143 $mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'in', $typesnounit); 144 145 // Add operator/value/unit group. 146 $mform->addGroup($elements, "{$this->name}_group", '', '', false); 147 148 // Date selectors for range operator. 149 $mform->addElement('date_selector', "{$this->name}_from", get_string('filterdatefrom', 'core_reportbuilder'), 150 ['optional' => true]); 151 $mform->setType("{$this->name}_from", PARAM_INT); 152 $mform->setDefault("{$this->name}_from", 0); 153 $mform->hideIf("{$this->name}_from", "{$this->name}_operator", 'neq', self::DATE_RANGE); 154 155 $mform->addElement('date_selector', "{$this->name}_to", get_string('filterdateto', 'core_reportbuilder'), 156 ['optional' => true]); 157 $mform->setType("{$this->name}_to", PARAM_INT); 158 $mform->setDefault("{$this->name}_to", 0); 159 $mform->hideIf("{$this->name}_to", "{$this->name}_operator", 'neq', self::DATE_RANGE); 160 } 161 162 /** 163 * Return filter SQL 164 * 165 * @param array $values 166 * @return array 167 */ 168 public function get_sql_filter(array $values): array { 169 $fieldsql = $this->filter->get_field_sql(); 170 $params = $this->filter->get_field_params(); 171 172 $operator = (int) ($values["{$this->name}_operator"] ?? self::DATE_ANY); 173 $dateunitvalue = (int) ($values["{$this->name}_value"] ?? 1); 174 $dateunit = (int) ($values["{$this->name}_unit"] ?? self::DATE_UNIT_DAY); 175 176 switch ($operator) { 177 case self::DATE_NOT_EMPTY: 178 $sql = "COALESCE({$fieldsql}, 0) <> 0"; 179 break; 180 case self::DATE_EMPTY: 181 $sql = "COALESCE({$fieldsql}, 0) = 0"; 182 break; 183 case self::DATE_RANGE: 184 $sql = ''; 185 186 $datefrom = (int)($values["{$this->name}_from"] ?? 0); 187 $dateto = (int)($values["{$this->name}_to"] ?? 0); 188 189 [$paramdatefrom, $paramdateto] = database::generate_param_names(2); 190 191 if ($datefrom > 0 && $dateto > 0) { 192 $sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}"; 193 $params[$paramdatefrom] = $datefrom; 194 $params[$paramdateto] = $dateto; 195 } else if ($datefrom > 0) { 196 $sql = "{$fieldsql} >= :{$paramdatefrom}"; 197 $params[$paramdatefrom] = $datefrom; 198 } else if ($dateto > 0) { 199 $sql = "{$fieldsql} < :{$paramdateto}"; 200 $params[$paramdateto] = $dateto; 201 } 202 203 break; 204 case self::DATE_BEFORE: 205 $param = database::generate_param_name(); 206 207 // We can use the start date of the "Last" operator as the end date here. 208 $sql = "{$fieldsql} < :{$param}"; 209 $params[$param] = self::get_relative_timeframe(self::DATE_LAST, $dateunitvalue, $dateunit)[0]; 210 break; 211 // Relative helper method can handle these three cases. 212 case self::DATE_LAST: 213 case self::DATE_CURRENT: 214 case self::DATE_NEXT: 215 216 // Last and next operators require a unit value greater than zero. 217 if ($operator !== self::DATE_CURRENT && $dateunitvalue === 0) { 218 return ['', []]; 219 } 220 221 // Generate parameters and SQL clause for the relative date comparison. 222 [$paramdatefrom, $paramdateto] = database::generate_param_names(2); 223 $sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}"; 224 225 [ 226 $params[$paramdatefrom], 227 $params[$paramdateto], 228 ] = self::get_relative_timeframe($operator, $dateunitvalue, $dateunit); 229 230 break; 231 case self::DATE_PAST: 232 $param = database::generate_param_name(); 233 $sql = "{$fieldsql} < :{$param}"; 234 $params[$param] = time(); 235 break; 236 case self::DATE_FUTURE: 237 $param = database::generate_param_name(); 238 $sql = "{$fieldsql} > :{$param}"; 239 $params[$param] = time(); 240 break; 241 default: 242 // Invalid or inactive filter. 243 return ['', []]; 244 } 245 246 return [$sql, $params]; 247 } 248 249 /** 250 * Return start and end time of given relative date period 251 * 252 * @param int $operator One of the ::DATE_LAST/CURRENT/NEXT constants 253 * @param int $dateunitvalue Unit multiplier of the date unit 254 * @param int $dateunit One of the ::DATE_UNIT_* constants 255 * @return int[] Timestamps representing the start/end of timeframe 256 */ 257 private static function get_relative_timeframe(int $operator, int $dateunitvalue, int $dateunit): array { 258 // Initialise start/end time to now. 259 $datestart = $dateend = new DateTimeImmutable(); 260 261 switch ($dateunit) { 262 case self::DATE_UNIT_HOUR: 263 if ($operator === self::DATE_CURRENT) { 264 $hour = (int) $datestart->format('G'); 265 $datestart = $datestart->setTime($hour, 0); 266 $dateend = $dateend->setTime($hour, 59, 59); 267 } else if ($operator === self::DATE_LAST) { 268 $datestart = $datestart->modify("-{$dateunitvalue} hour"); 269 } else if ($operator === self::DATE_NEXT) { 270 $dateend = $dateend->modify("+{$dateunitvalue} hour"); 271 } 272 break; 273 case self::DATE_UNIT_DAY: 274 if ($operator === self::DATE_CURRENT) { 275 $datestart = $datestart->setTime(0, 0); 276 $dateend = $dateend->setTime(23, 59, 59); 277 } else if ($operator === self::DATE_LAST) { 278 $datestart = $datestart->modify("-{$dateunitvalue} day"); 279 } else if ($operator === self::DATE_NEXT) { 280 $dateend = $dateend->modify("+{$dateunitvalue} day"); 281 } 282 283 break; 284 case self::DATE_UNIT_WEEK: 285 if ($operator === self::DATE_CURRENT) { 286 // The first day of the week is determined by site calendar configuration/preferences. 287 $startweekday = \core_calendar\type_factory::get_calendar_instance()->get_starting_weekday(); 288 $weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; 289 290 // If calculated start of week is after today (today is Tues/start of week is Weds), move back a week. 291 $datestartnow = $datestart->getTimestamp(); 292 $datestart = $datestart->modify($weekdays[$startweekday] . ' this week')->setTime(0, 0); 293 if ($datestart->getTimestamp() > $datestartnow) { 294 $datestart = $datestart->modify('-1 week'); 295 } 296 297 $dateend = $datestart->modify('+6 day')->setTime(23, 59, 59); 298 } else if ($operator === self::DATE_LAST) { 299 $datestart = $datestart->modify("-{$dateunitvalue} week"); 300 } else if ($operator === self::DATE_NEXT) { 301 $dateend = $dateend->modify("+{$dateunitvalue} week"); 302 } 303 304 break; 305 case self::DATE_UNIT_MONTH: 306 if ($operator === self::DATE_CURRENT) { 307 $datestart = $datestart->modify('first day of this month')->setTime(0, 0); 308 $dateend = $dateend->modify('last day of this month')->setTime(23, 59, 59); 309 } else if ($operator === self::DATE_LAST) { 310 $datestart = $datestart->modify("-{$dateunitvalue} month"); 311 } else if ($operator === self::DATE_NEXT) { 312 $dateend = $dateend->modify("+{$dateunitvalue} month"); 313 } 314 315 break; 316 case self::DATE_UNIT_YEAR: 317 if ($operator === self::DATE_CURRENT) { 318 $datestart = $datestart->modify('first day of january this year')->setTime(0, 0); 319 $dateend = $dateend->modify('last day of december this year')->setTime(23, 59, 59); 320 } else if ($operator === self::DATE_LAST) { 321 $datestart = $datestart->modify("-{$dateunitvalue} year"); 322 } else if ($operator === self::DATE_NEXT) { 323 $dateend = $dateend->modify("+{$dateunitvalue} year"); 324 } 325 326 break; 327 } 328 329 return [ 330 $datestart->getTimestamp(), 331 $dateend->getTimestamp(), 332 ]; 333 } 334 335 /** 336 * Return sample filter values 337 * 338 * @return array 339 */ 340 public function get_sample_values(): array { 341 return [ 342 "{$this->name}_operator" => self::DATE_CURRENT, 343 "{$this->name}_unit" => self::DATE_UNIT_WEEK, 344 ]; 345 } 346 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body