From a2f09b305c01ce47b201e9dc2afa4fb04a39cc5f Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 17 Dec 2021 17:27:29 +0100 Subject: [PATCH] Possible fix for https://github.com/firefly-iii/firefly-iii/issues/5405 --- .../Collector/Extensions/TimeCollection.php | 57 +++++++ app/Helpers/Collector/GroupCollector.php | 12 ++ .../Collector/GroupCollectorInterface.php | 11 ++ app/Support/ParseDateString.php | 134 ++++++--------- app/Support/Search/OperatorQuerySearch.php | 159 +++++++++++++----- config/firefly.php | 2 +- 6 files changed, 250 insertions(+), 125 deletions(-) diff --git a/app/Helpers/Collector/Extensions/TimeCollection.php b/app/Helpers/Collector/Extensions/TimeCollection.php index 86c45a6f92..938fc29315 100644 --- a/app/Helpers/Collector/Extensions/TimeCollection.php +++ b/app/Helpers/Collector/Extensions/TimeCollection.php @@ -119,4 +119,61 @@ trait TimeCollection return $this; } + + public function yearIs(string $year): GroupCollectorInterface + { + $this->query->whereYear('transaction_journals.date', '=', $year); + return $this; + } + + public function monthIs(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '=', $month); + return $this; + + } + + public function dayIs(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '=', $day); + return $this; + } + + public function yearBefore(string $year): GroupCollectorInterface + { + $this->query->whereYear('transaction_journals.date', '<=', $year); + return $this; + } + + public function monthBefore(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '<=', $month); + return $this; + + } + + public function dayBefore(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '<=', $day); + return $this; + } + + public function yearAfter(string $year): GroupCollectorInterface + { + $this->query->whereYear('transaction_journals.date', '>=', $year); + return $this; + } + + public function monthAfter(string $month): GroupCollectorInterface + { + $this->query->whereMonth('transaction_journals.date', '>=', $month); + return $this; + + } + + public function dayAfter(string $day): GroupCollectorInterface + { + $this->query->whereDay('transaction_journals.date', '>=', $day); + return $this; + } } diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 4fbf2e1bb2..6189019401 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -239,6 +239,9 @@ class GroupCollector implements GroupCollectorInterface { $result = $this->query->get($this->fields); + Log::debug('Query in full'); + $this->dumpQueryInLogs(); + // now to parse this into an array. $collection = $this->parseArray($result); $this->total = $collection->count(); @@ -559,6 +562,15 @@ class GroupCollector implements GroupCollectorInterface echo ''; } + /** + * + */ + public function dumpQueryInLogs(): void + { + Log::debug($this->query->select($this->fields)->toSql()) ; + Log::debug('Bindings',$this->query->getBindings()); + } + /** * Convert a selected set of fields to arrays. * diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index 3e58f2fa1c..ccbee0145e 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -565,4 +565,15 @@ interface GroupCollectorInterface */ public function withoutTags(): GroupCollectorInterface; + + public function yearIs(string $year): GroupCollectorInterface; + public function monthIs(string $month): GroupCollectorInterface; + public function dayIs(string $day): GroupCollectorInterface; + public function yearBefore(string $year): GroupCollectorInterface; + public function monthBefore(string $month): GroupCollectorInterface; + public function dayBefore(string $day): GroupCollectorInterface; + public function yearAfter(string $year): GroupCollectorInterface; + public function monthAfter(string $month): GroupCollectorInterface; + public function dayAfter(string $day): GroupCollectorInterface; + } diff --git a/app/Support/ParseDateString.php b/app/Support/ParseDateString.php index bca98b29d6..99dca10c9e 100644 --- a/app/Support/ParseDateString.php +++ b/app/Support/ParseDateString.php @@ -98,17 +98,17 @@ class ParseDateString return $this->parseRelativeDate($date); } if ('xxxx-xx-xx' === strtolower($date)) { - throw new FireflyException(sprintf('[a]Not a recognised date format: "%s"', $date)); + throw new FireflyException(sprintf('[a] Not a recognised date format: "%s"', $date)); } // can't do a partial year: $substrCount = substr_count(substr($date, 0, 4), 'x', 0); if (10 === strlen($date) && $substrCount > 0 && $substrCount < 4) { - throw new FireflyException(sprintf('[b]Not a recognised date format: "%s"', $date)); + throw new FireflyException(sprintf('[b] Not a recognised date format: "%s"', $date)); } // maybe a date range if (10 === strlen($date) && (str_contains($date, 'xx') || str_contains($date, 'xxxx'))) { - Log::debug(sprintf('[c]Detected a date range ("%s"), return a fake date.', $date)); + Log::debug(sprintf('[c] Detected a date range ("%s"), return a fake date.', $date)); // very lazy way to parse the date without parsing it, because this specific function // cant handle date ranges. return new Carbon('1984-09-17'); @@ -211,33 +211,31 @@ class ParseDateString /** * @param string $date - * @param Carbon $journalDate * * @return array */ - public function parseRange(string $date, Carbon $journalDate): array + public function parseRange(string $date): array { // several types of range can be submitted switch (true) { default: break; case $this->isDayRange($date): - return $this->parseDayRange($date, $journalDate); + return $this->parseDayRange($date); case $this->isMonthRange($date): - return $this->parseMonthRange($date, $journalDate); + return $this->parseMonthRange($date); case $this->isYearRange($date): - return $this->parseYearRange($date, $journalDate); + return $this->parseYearRange($date); case $this->isMonthDayRange($date): - return $this->parseMonthDayRange($date, $journalDate); + return $this->parseMonthDayRange($date); case $this->isDayYearRange($date): - return $this->parseDayYearRange($date, $journalDate); + return $this->parseDayYearRange($date); case $this->isMonthYearRange($date): - return $this->parseMonthYearRange($date, $journalDate); + return $this->parseMonthYearRange($date); } return [ - 'start' => new Carbon('1984-09-17'), - 'end' => new Carbon('1984-09-17'), + 'exact' => new Carbon('1984-09-17'), ]; } @@ -261,23 +259,18 @@ class ParseDateString } /** + * format of string is xxxx-xx-DD + * * @param string $date - * @param Carbon $journalDate * * @return array */ - protected function parseDayRange(string $date, Carbon $journalDate): array + protected function parseDayRange(string $date): array { - // format of string is xxxx-xx-DD - $validDate = str_replace(['xxxx'], [$journalDate->year], $date); - $validDate = str_replace(['xx'], [$journalDate->format('m')], $validDate); - Log::debug(sprintf('parseDayRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay(); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'day' => $parts[2], ]; } @@ -301,29 +294,19 @@ class ParseDateString } /** + * format of string is xxxx-MM-xx + * * @param string $date - * @param Carbon $journalDate * * @return array */ - protected function parseMonthRange(string $date, Carbon $journalDate): array + protected function parseMonthRange(string $date): array { - // because 31 would turn February into March unexpectedly and the exact day is irrelevant here. - $day = $journalDate->format('d'); - if ((int)$day > 28) { - $day = '28'; - } - - // format of string is xxxx-MM-xx - $validDate = str_replace(['xxxx'], [$journalDate->year], $date); - $validDate = str_replace(['xx'], [$day], $validDate); - Log::debug(sprintf('parseMonthRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth(); + Log::debug(sprintf('parseMonthRange: Parsed "%s".', $date)); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'month' => $parts[1], ]; } @@ -347,24 +330,19 @@ class ParseDateString } /** + * format of string is YYYY-xx-xx + * * @param string $date - * @param Carbon $journalDate * * @return array */ - protected function parseYearRange(string $date, Carbon $journalDate): array + protected function parseYearRange(string $date): array { - // format of string is YYYY-xx-xx - // kind of a convulted way of replacing variables but I'm lazy. - $validDate = str_replace(['xx-xx'], [sprintf('%s-xx', $journalDate->format('m'))], $date); - $validDate = str_replace(['xx'], [$journalDate->format('d')], $validDate); - Log::debug(sprintf('parseYearRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfYear(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfYear(); + Log::debug(sprintf('parseYearRange: Parsed "%s"', $date)); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'year' => $parts[0], ]; } @@ -388,23 +366,20 @@ class ParseDateString } /** + * format of string is xxxx-MM-DD + * * @param string $date - * @param Carbon $journalDate * * @return array */ - private function parseMonthDayRange(string $date, Carbon $journalDate): array + private function parseMonthDayRange(string $date): array { - // Any year. - // format of string is xxxx-MM-DD - $validDate = str_replace(['xxxx'], [$journalDate->year], $date); - Log::debug(sprintf('parseMonthDayRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay(); + Log::debug(sprintf('parseMonthDayRange: Parsed "%s".', $date)); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'month' => $parts[1], + 'day' => $parts[2], ]; } @@ -428,23 +403,20 @@ class ParseDateString } /** + * format of string is YYYY-xx-DD + * * @param string $date - * @param Carbon $journalDate * * @return array */ - private function parseDayYearRange(string $date, Carbon $journalDate): array + private function parseDayYearRange(string $date): array { - // Any year. - // format of string is YYYY-xx-DD - $validDate = str_replace(['xx'], [$journalDate->format('m')], $date); - Log::debug(sprintf('parseDayYearRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfDay(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfDay(); + Log::debug(sprintf('parseDayYearRange: Parsed "%s".', $date)); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'year' => $parts[0], + 'day' => $parts[2], ]; } @@ -468,28 +440,20 @@ class ParseDateString } /** + * format of string is YYYY-MM-xx + * * @param string $date - * @param Carbon $journalDate * * @return array */ - protected function parseMonthYearRange(string $date, Carbon $journalDate): array + protected function parseMonthYearRange(string $date): array { - // because 31 would turn February into March unexpectedly and the exact day is irrelevant here. - $day = $journalDate->format('d'); - if ((int)$day > 28) { - $day = '28'; - } - - // format of string is YYYY-MM-xx - $validDate = str_replace(['xx'], [$day], $date); - Log::debug(sprintf('parseMonthYearRange: Parsed "%s" into "%s"', $date, $validDate)); - $start = Carbon::createFromFormat('Y-m-d', $validDate)->startOfMonth(); - $end = Carbon::createFromFormat('Y-m-d', $validDate)->endOfMonth(); + Log::debug(sprintf('parseMonthYearRange: Parsed "%s".', $date)); + $parts = explode('-', $date); return [ - 'start' => $start, - 'end' => $end, + 'year' => $parts[0], + 'month' => $parts[1], ]; } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 7dbf35bf45..e89927ee62 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -606,48 +606,15 @@ class OperatorQuerySearch implements SearchInterface // case 'date_is': $range = $this->parseDateRange($value); - Log::debug( - sprintf( - 'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'), - $range['end']->format('Y-m-d') - ) - ); - $this->collector->setRange($range['start'], $range['end']); - - // add to operators manually: - $this->operators->push(['type' => 'date_before', 'value' => $range['start']->format('Y-m-d'),]); - $this->operators->push(['type' => 'date_after', 'value' => $range['end']->format('Y-m-d'),]); - + $this->setExactDateParams($range); return false; case 'date_before': - Log::debug(sprintf('Value for date_before is "%s"', $value)); $range = $this->parseDateRange($value); - Log::debug( - sprintf( - 'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'), - $range['end']->format('Y-m-d') - ) - ); - - // add to operators manually: - $this->operators->push(['type' => 'date_before', 'value' => $range['start']->format('Y-m-d'),]); - $this->collector->setBefore($range['start']); - + $this->setDateBeforeParams($range); return false; case 'date_after': - Log::debug(sprintf('Value for date_after is "%s"', $value)); $range = $this->parseDateRange($value); - Log::debug( - sprintf( - 'Set "%s" using collector with value "%s" (%s - %s)', $operator, $value, $range['start']->format('Y-m-d'), - $range['end']->format('Y-m-d') - ) - ); - - // add to operators manually: - $this->operators->push(['type' => 'date_after', 'value' => $range['end']->format('Y-m-d'),]); - $this->collector->setAfter($range['end']); - + $this->setDateAfterParams($range); return false; case 'created_on': Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value)); @@ -867,13 +834,12 @@ class OperatorQuerySearch implements SearchInterface { $parser = new ParseDateString; if ($parser->isDateRange($value)) { - return $parser->parseRange($value, $this->date); + return $parser->parseRange($value); } $parsedDate = $parser->parseDate($value); return [ - 'start' => $parsedDate, - 'end' => $parsedDate, + 'exact' => $parsedDate, ]; } @@ -884,4 +850,119 @@ class OperatorQuerySearch implements SearchInterface { return $this->words; } + + /** + * @param array $range + * + * @throws FireflyException + */ + private function setExactDateParams(array $range): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setExactParameters()', $key)); + case 'exact': + Log::debug(sprintf('Set date_is_exact value "%s"', $value->format('Y-m-d'))); + $this->collector->setRange($value, $value); + $this->operators->push(['type' => 'date_is', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_exact YEAR value "%s"', $value)); + $this->collector->yearIs($value); + $this->operators->push(['type' => 'date_is_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_exact MONTH value "%s"', $value)); + $this->collector->monthIs($value); + $this->operators->push(['type' => 'date_is_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_exact DAY value "%s"', $value)); + $this->collector->dayIs($value); + $this->operators->push(['type' => 'date_is_day', 'value' => $value,]); + break; + } + } + } + + /** + * @param array $range + * + * @throws FireflyException + */ + private function setDateBeforeParams(array $range): void + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setDateBeforeParams()', $key)); + case 'exact': + $this->collector->setBefore($value); + $this->operators->push(['type' => 'date_before', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_before YEAR value "%s"', $value)); + $this->collector->yearBefore($value); + $this->operators->push(['type' => 'date_before_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_before MONTH value "%s"', $value)); + $this->collector->monthBefore($value); + $this->operators->push(['type' => 'date_before_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_before DAY value "%s"', $value)); + $this->collector->dayBefore($value); + $this->operators->push(['type' => 'date_before_day', 'value' => $value,]); + break; + } + } + } + + /** + * @param array $range + * + * @throws FireflyException + */ + private function setDateAfterParams(array $range) + { + /** + * @var string $key + * @var Carbon|string $value + */ + foreach ($range as $key => $value) { + switch ($key) { + default: + throw new FireflyException(sprintf('Cannot handle key "%s" in setDateAfterParams()', $key)); + case 'exact': + $this->collector->setAfter($value); + $this->operators->push(['type' => 'date_after', 'value' => $value->format('Y-m-d'),]); + break; + case 'year': + Log::debug(sprintf('Set date_is_after YEAR value "%s"', $value)); + $this->collector->yearAfter($value); + $this->operators->push(['type' => 'date_after_year', 'value' => $value,]); + break; + case 'month': + Log::debug(sprintf('Set date_is_after MONTH value "%s"', $value)); + $this->collector->monthAfter($value); + $this->operators->push(['type' => 'date_after_month', 'value' => $value,]); + break; + case 'day': + Log::debug(sprintf('Set date_is_after DAY value "%s"', $value)); + $this->collector->dayAfter($value); + $this->operators->push(['type' => 'date_after_day', 'value' => $value,]); + break; + } + } + } } diff --git a/config/firefly.php b/config/firefly.php index 40e14df91c..78992acc9e 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -101,7 +101,7 @@ return [ 'webhooks' => true, 'handle_debts' => true, ], - 'version' => '5.6.6', + 'version' => '5.6.7', 'api_version' => '1.5.5', 'db_version' => 18,