extend('taskLexer', ...)): * - `duemonth:VALUE` -- padDays 0: due within the target month. * - `aroundmonth:VALUE` -- padDays 10: the month minus 10 days .. plus 10 days (i.e. the last * 10 days of the previous month, the whole month, and the first 10 days of the next month). * * VALUE is `this` (also `next` / `last`) or an explicit month such as `2026-01` or `january-2026` * (anything strtotime() can parse). Bounds are computed with mktime (correct for any month length) * at search time, so the filter auto-rolls. * * Both bounds (gte start, lte end) are added inside this single apply() on the single-value path, * so they AND -- the range Kanboard's saved-filter string syntax cannot express (repeating the * same attribute ORs via OrCriteria, and there is no AND keyword). */ class TaskMonthRangeFilter extends BaseFilter implements FilterInterface { private $attribute = 'duemonth'; private $padDays = 0; public function setAttribute($attribute) { $this->attribute = $attribute; return $this; } public function setPadDays($days) { $this->padDays = (int) $days; return $this; } public function getAttributes() { return array($this->attribute); } public function apply() { $reference = $this->resolveMonthTimestamp((string) $this->value); if ($reference === false) { return $this; // unknown/typo value -> no-op (matches nothing added) } $month = (int) date('n', $reference); $year = (int) date('Y', $reference); $first = mktime(0, 0, 0, $month, 1, $year); $last = mktime(23, 59, 59, $month + 1, 0, $year); // day 0 of next month = last day of this $pad = $this->padDays * 86400; $this->query->neq(TaskModel::TABLE.'.date_due', 0); $this->query->gte(TaskModel::TABLE.'.date_due', $first - $pad); $this->query->lte(TaskModel::TABLE.'.date_due', $last + $pad); return $this; } /** * Resolve the filter value to any timestamp inside the target month (or false if unparseable). * * @param string $value * @return int|false */ private function resolveMonthTimestamp($value) { $value = strtolower(trim($value)); if ($value === '' || $value === 'this') { return strtotime('first day of this month'); } if ($value === 'next') { return strtotime('first day of next month'); } if ($value === 'last' || $value === 'prev' || $value === 'previous') { return strtotime('first day of last month'); } return strtotime($value); // 2026-01, january-2026, "last day of january 2026", ... } }