Differences Between: [Versions 400 and 401] [Versions 400 and 402] [Versions 400 and 403]
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 Relative date unit for a day */ 62 public const DATE_UNIT_DAY = 1; 63 64 /** @var int Relative date unit for a week */ 65 public const DATE_UNIT_WEEK = 2; 66 67 /** @var int Relative date unit for a month */ 68 public const DATE_UNIT_MONTH = 3; 69 70 /** @var int Relative date unit for a month */ 71 public const DATE_UNIT_YEAR = 4; 72 73 /** 74 * Return an array of operators available for this filter 75 * 76 * @return lang_string[] 77 */ 78 private function get_operators(): array { 79 $operators = [ 80 self::DATE_ANY => new lang_string('filterisanyvalue', 'core_reportbuilder'), 81 self::DATE_NOT_EMPTY => new lang_string('filterisnotempty', 'core_reportbuilder'), 82 self::DATE_EMPTY => new lang_string('filterisempty', 'core_reportbuilder'), 83 self::DATE_RANGE => new lang_string('filterrange', 'core_reportbuilder'), 84 self::DATE_LAST => new lang_string('filterdatelast', 'core_reportbuilder'), 85 self::DATE_CURRENT => new lang_string('filterdatecurrent', 'core_reportbuilder'), 86 self::DATE_NEXT => new lang_string('filterdatenext', 'core_reportbuilder'), 87 ]; 88 89 return $this->filter->restrict_limited_operators($operators); 90 } 91 92 /** 93 * Setup form 94 * 95 * @param MoodleQuickForm $mform 96 */ 97 public function setup_form(MoodleQuickForm $mform): void { 98 // Operator selector. 99 $operatorlabel = get_string('filterfieldoperator', 'core_reportbuilder', $this->get_header()); 100 101 $elements[] = $mform->createElement('select', "{$this->name}_operator", $operatorlabel, $this->get_operators()); 102 $mform->setType("{$this->name}_operator", PARAM_INT); 103 $mform->setDefault("{$this->name}_operator", self::DATE_ANY); 104 105 // Value selector for last and next operators. 106 $valuelabel = get_string('filterfieldvalue', 'core_reportbuilder', $this->get_header()); 107 108 $elements[] = $mform->createElement('text', "{$this->name}_value", $valuelabel, ['size' => 3]); 109 $mform->setType("{$this->name}_value", PARAM_INT); 110 $mform->setDefault("{$this->name}_value", 1); 111 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_ANY); 112 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_NOT_EMPTY); 113 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_EMPTY); 114 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_RANGE); 115 $mform->hideIf("{$this->name}_value", "{$this->name}_operator", 'eq', self::DATE_CURRENT); 116 117 // Unit selector for last and next operators. 118 $unitlabel = get_string('filterdurationunit', 'core_reportbuilder', $this->get_header()); 119 $units = [ 120 self::DATE_UNIT_DAY => get_string('filterdatedays', 'core_reportbuilder'), 121 self::DATE_UNIT_WEEK => get_string('filterdateweeks', 'core_reportbuilder'), 122 self::DATE_UNIT_MONTH => get_string('filterdatemonths', 'core_reportbuilder'), 123 self::DATE_UNIT_YEAR => get_string('filterdateyears', 'core_reportbuilder'), 124 ]; 125 126 $elements[] = $mform->createElement('select', "{$this->name}_unit", $unitlabel, $units); 127 $mform->setType("{$this->name}_unit", PARAM_INT); 128 $mform->setDefault("{$this->name}_unit", self::DATE_UNIT_DAY); 129 $mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_ANY); 130 $mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_NOT_EMPTY); 131 $mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_EMPTY); 132 $mform->hideIf("{$this->name}_unit", "{$this->name}_operator", 'eq', self::DATE_RANGE); 133 134 // Add operator/value/unit group. 135 $mform->addGroup($elements, "{$this->name}_group", '', '', false); 136 137 // Date selectors for range operator. 138 $mform->addElement('date_selector', "{$this->name}_from", get_string('filterdatefrom', 'core_reportbuilder'), 139 ['optional' => true]); 140 $mform->setType("{$this->name}_from", PARAM_INT); 141 $mform->setDefault("{$this->name}_from", 0); 142 $mform->hideIf("{$this->name}_from", "{$this->name}_operator", 'neq', self::DATE_RANGE); 143 144 $mform->addElement('date_selector', "{$this->name}_to", get_string('filterdateto', 'core_reportbuilder'), 145 ['optional' => true]); 146 $mform->setType("{$this->name}_to", PARAM_INT); 147 $mform->setDefault("{$this->name}_to", 0); 148 $mform->hideIf("{$this->name}_to", "{$this->name}_operator", 'neq', self::DATE_RANGE); 149 } 150 151 /** 152 * Return filter SQL 153 * 154 * @param array $values 155 * @return array 156 */ 157 public function get_sql_filter(array $values): array { 158 $fieldsql = $this->filter->get_field_sql(); 159 $params = $this->filter->get_field_params(); 160 161 $operator = (int) ($values["{$this->name}_operator"] ?? self::DATE_ANY); 162 $dateunitvalue = (int) ($values["{$this->name}_value"] ?? 1); 163 $dateunit = (int) ($values["{$this->name}_unit"] ?? self::DATE_UNIT_DAY); 164 165 switch ($operator) { 166 case self::DATE_NOT_EMPTY: 167 $sql = "COALESCE({$fieldsql}, 0) <> 0"; 168 break; 169 case self::DATE_EMPTY: 170 $sql = "COALESCE({$fieldsql}, 0) = 0"; 171 break; 172 case self::DATE_RANGE: 173 $sql = ''; 174 175 $datefrom = (int)($values["{$this->name}_from"] ?? 0); 176 $dateto = (int)($values["{$this->name}_to"] ?? 0); 177 178 $paramdatefrom = database::generate_param_name(); 179 $paramdateto = database::generate_param_name(); 180 181 if ($datefrom > 0 && $dateto > 0) { 182 $sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}"; 183 $params[$paramdatefrom] = $datefrom; 184 $params[$paramdateto] = $dateto; 185 } else if ($datefrom > 0) { 186 $sql = "{$fieldsql} >= :{$paramdatefrom}"; 187 $params[$paramdatefrom] = $datefrom; 188 } else if ($dateto > 0) { 189 $sql = "{$fieldsql} < :{$paramdateto}"; 190 $params[$paramdateto] = $dateto; 191 } 192 193 break; 194 // Relative helper method can handle these three cases. 195 case self::DATE_LAST: 196 case self::DATE_CURRENT: 197 case self::DATE_NEXT: 198 199 // Last and next operators require a unit value greater than zero. 200 if ($operator !== self::DATE_CURRENT && $dateunitvalue === 0) { 201 return ['', []]; 202 } 203 204 $paramdatefrom = database::generate_param_name(); 205 $paramdateto = database::generate_param_name(); 206 207 $sql = "{$fieldsql} BETWEEN :{$paramdatefrom} AND :{$paramdateto}"; 208 [ 209 $params[$paramdatefrom], 210 $params[$paramdateto], 211 ] = self::get_relative_timeframe($operator, $dateunitvalue, $dateunit); 212 213 break; 214 default: 215 // Invalid or inactive filter. 216 return ['', []]; 217 } 218 219 return [$sql, $params]; 220 } 221 222 /** 223 * Return start and end time of given relative date period 224 * 225 * @param int $operator One of the ::DATE_LAST/CURRENT/NEXT constants 226 * @param int $dateunitvalue Unit multiplier of the date unit 227 * @param int $dateunit One of the ::DATE_UNIT_DAY/WEEK/MONTH/YEAR constants 228 * @return int[] Timestamps representing the start/end of timeframe 229 */ 230 private static function get_relative_timeframe(int $operator, int $dateunitvalue, int $dateunit): array { 231 // Initialise start/end time to now. 232 $datestart = $dateend = new DateTimeImmutable(); 233 234 switch ($dateunit) { 235 case self::DATE_UNIT_DAY: 236 if ($operator === self::DATE_CURRENT) { 237 $datestart = $datestart->setTime(0, 0); 238 $dateend = $dateend->setTime(23, 59, 59); 239 } else if ($operator === self::DATE_LAST) { 240 $datestart = $datestart->modify("-{$dateunitvalue} day"); 241 } else if ($operator === self::DATE_NEXT) { 242 $dateend = $dateend->modify("+{$dateunitvalue} day"); 243 } 244 245 break; 246 case self::DATE_UNIT_WEEK: 247 if ($operator === self::DATE_CURRENT) { 248 // The first day of the week is determined by site calendar configuration/preferences. 249 $startweekday = \core_calendar\type_factory::get_calendar_instance()->get_starting_weekday(); 250 $weekdays = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']; 251 252 // If calculated start of week is after today (today is Tues/start of week is Weds), move back a week. 253 $datestartnow = $datestart->getTimestamp(); 254 $datestart = $datestart->modify($weekdays[$startweekday] . ' this week')->setTime(0, 0); 255 if ($datestart->getTimestamp() > $datestartnow) { 256 $datestart = $datestart->modify('-1 week'); 257 } 258 259 $dateend = $datestart->modify('+6 day')->setTime(23, 59, 59); 260 } else if ($operator === self::DATE_LAST) { 261 $datestart = $datestart->modify("-{$dateunitvalue} week"); 262 } else if ($operator === self::DATE_NEXT) { 263 $dateend = $dateend->modify("+{$dateunitvalue} week"); 264 } 265 266 break; 267 case self::DATE_UNIT_MONTH: 268 if ($operator === self::DATE_CURRENT) { 269 $datestart = $datestart->modify('first day of this month')->setTime(0, 0); 270 $dateend = $dateend->modify('last day of this month')->setTime(23, 59, 59); 271 } else if ($operator === self::DATE_LAST) { 272 $datestart = $datestart->modify("-{$dateunitvalue} month"); 273 } else if ($operator === self::DATE_NEXT) { 274 $dateend = $dateend->modify("+{$dateunitvalue} month"); 275 } 276 277 break; 278 case self::DATE_UNIT_YEAR: 279 if ($operator === self::DATE_CURRENT) { 280 $datestart = $datestart->modify('first day of january this year')->setTime(0, 0); 281 $dateend = $dateend->modify('last day of december this year')->setTime(23, 59, 59); 282 } else if ($operator === self::DATE_LAST) { 283 $datestart = $datestart->modify("-{$dateunitvalue} year"); 284 } else if ($operator === self::DATE_NEXT) { 285 $dateend = $dateend->modify("+{$dateunitvalue} year"); 286 } 287 288 break; 289 } 290 291 return [ 292 $datestart->getTimestamp(), 293 $dateend->getTimestamp(), 294 ]; 295 } 296 297 /** 298 * Return sample filter values 299 * 300 * @return array 301 */ 302 public function get_sample_values(): array { 303 return [ 304 "{$this->name}_operator" => self::DATE_CURRENT, 305 "{$this->name}_unit" => self::DATE_UNIT_WEEK, 306 ]; 307 } 308 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body