Fix a lot of phpstan things

This commit is contained in:
James Cole 2023-11-26 12:10:42 +01:00
parent a6c355c7b8
commit 68f01d932e
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
53 changed files with 214 additions and 120 deletions

View File

@ -125,7 +125,7 @@ class AccountController extends Controller
'native_id' => (string)$default->id, 'native_id' => (string)$default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
'start' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end' => $end->toAtomString(), 'end' => $end->toAtomString(),
'period' => '1D', 'period' => '1D',

View File

@ -100,12 +100,12 @@ class BalanceController extends Controller
'currency_symbol' => $default->symbol, 'currency_symbol' => $default->symbol,
'currency_code' => $default->code, 'currency_code' => $default->code,
'currency_name' => $default->name, 'currency_name' => $default->name,
'currency_decimal_places' => (int)$default->decimal_places, 'currency_decimal_places' => $default->decimal_places,
'native_id' => (string)$defaultCurrencyId, 'native_id' => (string)$defaultCurrencyId,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_code' => $default->code, 'native_code' => $default->code,
'native_name' => $default->name, 'native_name' => $default->name,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
]; ];
@ -131,7 +131,7 @@ class BalanceController extends Controller
'native_id' => (string)$default->id, 'native_id' => (string)$default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
]; ];
// set the array (in monetary info) with spent/earned in this $period, if it does not exist. // set the array (in monetary info) with spent/earned in this $period, if it does not exist.

View File

@ -209,7 +209,7 @@ class BudgetController extends Controller
'native_code' => $this->currency->code, 'native_code' => $this->currency->code,
'native_name' => $this->currency->name, 'native_name' => $this->currency->name,
'native_symbol' => $this->currency->symbol, 'native_symbol' => $this->currency->symbol,
'native_decimal_places' => (int)$this->currency->decimal_places, 'native_decimal_places' => $this->currency->decimal_places,
'start' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end' => $end->toAtomString(), 'end' => $end->toAtomString(),
'spent' => '0', 'spent' => '0',

View File

@ -114,12 +114,12 @@ class CategoryController extends Controller
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_id' => (string)$default->id, 'native_id' => (string)$default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_name' => $default->name, 'native_name' => $default->name,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
'period' => null, 'period' => null,
'start' => $start->toAtomString(), 'start' => $start->toAtomString(),
'end' => $end->toAtomString(), 'end' => $end->toAtomString(),

View File

@ -376,7 +376,7 @@ class BasicController extends Controller
'currency_id' => (string)$default->id, 'currency_id' => (string)$default->id,
'currency_code' => $default->code, 'currency_code' => $default->code,
'currency_symbol' => $default->symbol, 'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places, 'currency_decimal_places' => $default->decimal_places,
]; ];
$nativePerDay = [ $nativePerDay = [
'key' => 'left-per-day-to-spend-in-native', 'key' => 'left-per-day-to-spend-in-native',
@ -384,7 +384,7 @@ class BasicController extends Controller
'currency_id' => (string)$default->id, 'currency_id' => (string)$default->id,
'currency_code' => $default->code, 'currency_code' => $default->code,
'currency_symbol' => $default->symbol, 'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places, 'currency_decimal_places' => $default->decimal_places,
]; ];
/** /**

View File

@ -61,7 +61,7 @@ class DeleteEmptyJournals extends Command
{ {
$set = Transaction::whereNull('deleted_at') $set = Transaction::whereNull('deleted_at')
->groupBy('transactions.transaction_journal_id') ->groupBy('transactions.transaction_journal_id')
->get([DB::raw('COUNT(transactions.transaction_journal_id) as the_count'), 'transaction_journal_id']); ->get([DB::raw('COUNT(transactions.transaction_journal_id) as the_count'), 'transaction_journal_id']); // @phpstan-ignore-line
$total = 0; $total = 0;
/** @var Transaction $row */ /** @var Transaction $row */
foreach ($set as $row) { foreach ($set as $row) {

View File

@ -51,7 +51,7 @@ class FixGroupAccounts extends Command
{ {
$groups = []; $groups = [];
$res = TransactionJournal::groupBy('transaction_group_id') $res = TransactionJournal::groupBy('transaction_group_id')
->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]); ->get(['transaction_group_id', DB::raw('COUNT(transaction_group_id) as the_count')]);// @phpstan-ignore-line
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
foreach ($res as $journal) { foreach ($res as $journal) {
if ((int)$journal->the_count > 1) { if ((int)$journal->the_count > 1) {

View File

@ -190,7 +190,12 @@ class ExportData extends Command
{ {
$date = today(config('app.timezone'))->subYear(); $date = today(config('app.timezone'))->subYear();
$error = false; $error = false;
if (null !== $this->option($field)) {
if (!in_array($field, ['start', 'end'], true)) {
throw new FireflyException(sprintf('Invalid field "%s" given, can only be "start" or "end".', $field));
}
if (is_string($this->option($field))) {
try { try {
$date = Carbon::createFromFormat('!Y-m-d', $this->option($field)); $date = Carbon::createFromFormat('!Y-m-d', $this->option($field));
} catch (InvalidArgumentException $e) { } catch (InvalidArgumentException $e) {
@ -198,6 +203,10 @@ class ExportData extends Command
$this->friendlyError(sprintf('%s date "%s" must be formatted YYYY-MM-DD. Field will be ignored.', $field, $this->option('start'))); $this->friendlyError(sprintf('%s date "%s" must be formatted YYYY-MM-DD. Field will be ignored.', $field, $this->option('start')));
$error = true; $error = true;
} }
if (false === $date) {
$this->friendlyError(sprintf('%s date "%s" must be formatted YYYY-MM-DD.', $field, $this->option('start')));
throw new FireflyException(sprintf('%s date "%s" must be formatted YYYY-MM-DD.', $field, $this->option('start')));
}
} }
if (null === $this->option($field)) { if (null === $this->option($field)) {
app('log')->info(sprintf('No date given in field "%s"', $field)); app('log')->info(sprintf('No date given in field "%s"', $field));
@ -208,12 +217,15 @@ class ExportData extends Command
$journal = $this->journalRepository->firstNull(); $journal = $this->journalRepository->firstNull();
$date = null === $journal ? today(config('app.timezone'))->subYear() : $journal->date; $date = null === $journal ? today(config('app.timezone'))->subYear() : $journal->date;
$date->startOfDay(); $date->startOfDay();
return $date;
} }
// field can only be 'end' at this point, so no need to include it in the check.
if (true === $error && 'end' === $field) { if (true === $error) {
$date = today(config('app.timezone')); $date = today(config('app.timezone'));
$date->endOfDay(); $date->endOfDay();
return $date;
} }
if ('end' === $field) { if ('end' === $field) {
$date->endOfDay(); $date->endOfDay();
} }

View File

@ -79,6 +79,7 @@ class CreateDatabase extends Command
// only continue when no error. // only continue when no error.
// with PDO, try to list DB's ( // with PDO, try to list DB's (
/** @var array $stmt */
$stmt = $pdo->query('SHOW DATABASES;'); $stmt = $pdo->query('SHOW DATABASES;');
// slightly more complex but less error-prone. // slightly more complex but less error-prone.
foreach ($stmt as $row) { foreach ($stmt as $row) {

View File

@ -253,13 +253,14 @@ class ForceDecimalSize extends Command
} }
/** @var Account $account */ /** @var Account $account */
foreach ($result as $account) { foreach ($result as $account) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$value = $account->$field; $value = $account->$field;
if (null === $value) { if (null === $value) {
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = 10** (int)$currency->decimal_places; $pow = 10** $currency->decimal_places;
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12); $correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->friendlyInfo(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct)); $this->friendlyInfo(sprintf('Account #%d has %s with value "%s", this has been corrected to "%s".', $account->id, $field, $value, $correct));
Account::find($account->id)->update([$field => $correct]); Account::find($account->id)->update([$field => $correct]);
@ -286,6 +287,7 @@ class ForceDecimalSize extends Command
/** @var Builder $query */ /** @var Builder $query */
$query = $class::where('transaction_currency_id', $currency->id)->where( $query = $class::where('transaction_currency_id', $currency->id)->where(
static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) { static function (Builder $q) use ($fields, $currency, $operator, $cast, $regularExpression) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$q->orWhere( $q->orWhere(
DB::raw(sprintf('CAST(%s AS %s)', $field, $cast)), // @phpstan-ignore-line DB::raw(sprintf('CAST(%s AS %s)', $field, $cast)), // @phpstan-ignore-line
@ -304,13 +306,14 @@ class ForceDecimalSize extends Command
} }
/** @var Model $item */ /** @var Model $item */
foreach ($result as $item) { foreach ($result as $item) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$value = $item->$field; $value = $item->$field;
if (null === $value) { if (null === $value) {
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = 10** (int)$currency->decimal_places; $pow = 10** $currency->decimal_places;
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12); $correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->friendlyWarning(sprintf('%s #%d has %s with value "%s", this has been corrected to "%s".', $table, $item->id, $field, $value, $correct)); $this->friendlyWarning(sprintf('%s #%d has %s with value "%s", this has been corrected to "%s".', $table, $item->id, $field, $value, $correct));
$class::find($item->id)->update([$field => $correct]); $class::find($item->id)->update([$field => $correct]);
@ -356,13 +359,14 @@ class ForceDecimalSize extends Command
} }
/** @var PiggyBankEvent $item */ /** @var PiggyBankEvent $item */
foreach ($result as $item) { foreach ($result as $item) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$value = $item->$field; $value = $item->$field;
if (null === $value) { if (null === $value) {
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = 10** (int)$currency->decimal_places; $pow = 10** $currency->decimal_places;
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12); $correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->friendlyWarning( $this->friendlyWarning(
sprintf('Piggy bank event #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct) sprintf('Piggy bank event #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct)
@ -410,13 +414,14 @@ class ForceDecimalSize extends Command
} }
/** @var PiggyBankRepetition $item */ /** @var PiggyBankRepetition $item */
foreach ($result as $item) { foreach ($result as $item) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$value = $item->$field; $value = $item->$field;
if (null === $value) { if (null === $value) {
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = 10** (int)$currency->decimal_places; $pow = 10** $currency->decimal_places;
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12); $correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->friendlyWarning( $this->friendlyWarning(
sprintf('Piggy bank repetition #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct) sprintf('Piggy bank repetition #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct)
@ -463,13 +468,14 @@ class ForceDecimalSize extends Command
} }
/** @var PiggyBank $item */ /** @var PiggyBank $item */
foreach ($result as $item) { foreach ($result as $item) {
/** @var string $field */
foreach ($fields as $field) { foreach ($fields as $field) {
$value = $item->$field; $value = $item->$field;
if (null === $value) { if (null === $value) {
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = 10** (int)$currency->decimal_places; $pow = 10** $currency->decimal_places;
$correct = bcdiv((string)round($value * $pow), (string)$pow, 12); $correct = bcdiv((string)round($value * $pow), (string)$pow, 12);
$this->friendlyWarning(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct)); $this->friendlyWarning(sprintf('Piggy bank #%d has %s with value "%s", this has been corrected to "%s".', $item->id, $field, $value, $correct));
PiggyBank::find($item->id)->update([$field => $correct]); PiggyBank::find($item->id)->update([$field => $correct]);
@ -506,7 +512,7 @@ class ForceDecimalSize extends Command
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = (float)10** (int)$currency->decimal_places; $pow = (float)10** $currency->decimal_places;
$correct = bcdiv((string)round((float)$value * $pow), (string)$pow, 12); $correct = bcdiv((string)round((float)$value * $pow), (string)$pow, 12);
$this->friendlyWarning(sprintf('Transaction #%d has amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct)); $this->friendlyWarning(sprintf('Transaction #%d has amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct));
Transaction::find($item->id)->update(['amount' => $correct]); Transaction::find($item->id)->update(['amount' => $correct]);
@ -533,7 +539,7 @@ class ForceDecimalSize extends Command
continue; continue;
} }
// fix $field by rounding it down correctly. // fix $field by rounding it down correctly.
$pow = (float)10** (int)$currency->decimal_places; $pow = (float)10** $currency->decimal_places;
$correct = bcdiv((string)round((float)$value * $pow), (string)$pow, 12); $correct = bcdiv((string)round((float)$value * $pow), (string)$pow, 12);
$this->friendlyWarning( $this->friendlyWarning(
sprintf('Transaction #%d has foreign amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct) sprintf('Transaction #%d has foreign amount with value "%s", this has been corrected to "%s".', $item->id, $value, $correct)

View File

@ -67,11 +67,13 @@ class ScanAttachments extends Command
$decryptedContent = $encryptedContent; $decryptedContent = $encryptedContent;
} }
$tempFileName = tempnam(sys_get_temp_dir(), 'FireflyIII'); $tempFileName = tempnam(sys_get_temp_dir(), 'FireflyIII');
if(false === $tempFileName) {
app('log')->error(sprintf('Could not create temporary file for attachment #%d', $attachment->id));
exit(1);
}
file_put_contents($tempFileName, $decryptedContent); file_put_contents($tempFileName, $decryptedContent);
$md5 = md5_file($tempFileName); $attachment->md5 = (string) md5_file($tempFileName);
$mime = mime_content_type($tempFileName); $attachment->mime = (string) mime_content_type($tempFileName);
$attachment->md5 = $md5;
$attachment->mime = $mime;
$attachment->save(); $attachment->save();
$this->friendlyInfo(sprintf('Fixed attachment #%d', $attachment->id)); $this->friendlyInfo(sprintf('Fixed attachment #%d', $attachment->id));
} }

View File

@ -61,14 +61,15 @@ class UpgradeFireflyInstructions extends Command
*/ */
private function updateInstructions(): void private function updateInstructions(): void
{ {
/** @var string $version */ $version = (string) config('firefly.version');
$version = config('firefly.version'); /** @var array $config */
$config = config('upgrade.text.upgrade'); $config = config('upgrade.text.upgrade');
$text = ''; $text = '';
/** @var string $compare */
foreach (array_keys($config) as $compare) { foreach (array_keys($config) as $compare) {
// if string starts with: // if string starts with:
if (str_starts_with($version, $compare)) { if (str_starts_with($version, $compare)) {
$text = $config[$compare]; $text = (string) $config[$compare];
} }
} }
@ -78,7 +79,7 @@ class UpgradeFireflyInstructions extends Command
$this->showLine(); $this->showLine();
$this->boxed(''); $this->boxed('');
if (null === $text || '' === $text) { if ('' === $text) {
$this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version)); $this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version));
$this->boxedInfo('There are no extra upgrade instructions.'); $this->boxedInfo('There are no extra upgrade instructions.');
$this->boxed('Firefly III should be ready for use.'); $this->boxed('Firefly III should be ready for use.');
@ -174,14 +175,15 @@ class UpgradeFireflyInstructions extends Command
*/ */
private function installInstructions(): void private function installInstructions(): void
{ {
/** @var string $version */ $version = (string) config('firefly.version');
$version = config('firefly.version'); /** @var array $config */
$config = config('upgrade.text.install'); $config = config('upgrade.text.install');
$text = ''; $text = '';
/** @var string $compare */
foreach (array_keys($config) as $compare) { foreach (array_keys($config) as $compare) {
// if string starts with: // if string starts with:
if (str_starts_with($version, $compare)) { if (str_starts_with($version, $compare)) {
$text = $config[$compare]; $text = (string) $config[$compare];
} }
} }
$this->newLine(); $this->newLine();
@ -189,7 +191,7 @@ class UpgradeFireflyInstructions extends Command
$this->newLine(); $this->newLine();
$this->showLine(); $this->showLine();
$this->boxed(''); $this->boxed('');
if (null === $text || '' === $text) { if ('' === $text) {
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version)); $this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
$this->boxedInfo('There are no extra installation instructions.'); $this->boxedInfo('There are no extra installation instructions.');
$this->boxed('Firefly III should be ready for use.'); $this->boxed('Firefly III should be ready for use.');

View File

@ -37,6 +37,7 @@ use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface; use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Console\Command; use Illuminate\Console\Command;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/** /**
* Class ApplyRules * Class ApplyRules
@ -296,6 +297,10 @@ class ApplyRules extends Command
if (null !== $endString && '' !== $endString) { if (null !== $endString && '' !== $endString) {
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString); $inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
} }
if(false === $inputEnd || false === $inputStart) {
Log::error('Could not parse start or end date in verifyInputDate().');
return;
}
if ($inputStart > $inputEnd) { if ($inputStart > $inputEnd) {
[$inputEnd, $inputStart] = [$inputStart, $inputEnd]; [$inputEnd, $inputStart] = [$inputStart, $inputEnd];

View File

@ -120,11 +120,11 @@ class FixPostgresSequences extends Command
continue; continue;
} }
if ($nextId->nextval < $highestId->max) { if ($nextId->nextval < $highestId->max) { // @phpstan-ignore-line
DB::select(sprintf('SELECT setval(\'%s_id_seq\', %d)', $tableToCheck, $highestId->max)); DB::select(sprintf('SELECT setval(\'%s_id_seq\', %d)', $tableToCheck, $highestId->max));
$highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first(); $highestId = DB::table($tableToCheck)->select(DB::raw('MAX(id)'))->first();
$nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first(); $nextId = DB::table($tableToCheck)->select(DB::raw(sprintf('nextval(\'%s_id_seq\')', $tableToCheck)))->first();
if ($nextId->nextval > $highestId->max) { if ($nextId->nextval > $highestId->max) { // @phpstan-ignore-line
$this->friendlyInfo(sprintf('Table "%s" autoincrement corrected.', $tableToCheck)); $this->friendlyInfo(sprintf('Table "%s" autoincrement corrected.', $tableToCheck));
} }
if ($nextId->nextval <= $highestId->max) { if ($nextId->nextval <= $highestId->max) {

View File

@ -138,14 +138,15 @@ class MigrateToRules extends Command
/** @var Preference $lang */ /** @var Preference $lang */
$lang = app('preferences')->getForUser($user, 'language', 'en_US'); $lang = app('preferences')->getForUser($user, 'language', 'en_US');
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data); $language = null !== $lang->data && !is_array($lang->data) ? (string)$lang->data : 'en_US';
$groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $language);
$ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle); $ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle);
if (null === $ruleGroup) { if (null === $ruleGroup) {
$ruleGroup = $this->ruleGroupRepository->store( $ruleGroup = $this->ruleGroupRepository->store(
[ [
'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data), 'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $language),
'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data), 'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $language),
'active' => true, 'active' => true,
] ]
); );
@ -168,6 +169,7 @@ class MigrateToRules extends Command
if ('MIGRATED_TO_RULES' === $bill->match) { if ('MIGRATED_TO_RULES' === $bill->match) {
return; return;
} }
$languageString = null !== $language->data && !is_array($language->data) ? (string)$language->data : 'en_US';
// get match thing: // get match thing:
$match = implode(' ', explode(',', $bill->match)); $match = implode(' ', explode(',', $bill->match));
@ -176,8 +178,8 @@ class MigrateToRules extends Command
'active' => true, 'active' => true,
'strict' => false, 'strict' => false,
'stop_processing' => false, // field is no longer used. 'stop_processing' => false, // field is no longer used.
'title' => (string)trans('firefly.rule_for_bill_title', ['name' => $bill->name], $language->data), 'title' => (string)trans('firefly.rule_for_bill_title', ['name' => $bill->name], $languageString),
'description' => (string)trans('firefly.rule_for_bill_description', ['name' => $bill->name], $language->data), 'description' => (string)trans('firefly.rule_for_bill_description', ['name' => $bill->name], $languageString),
'trigger' => 'store-journal', 'trigger' => 'store-journal',
'triggers' => [ 'triggers' => [
[ [

View File

@ -150,7 +150,7 @@ class UpgradeCurrencyPreferences extends Command
{ {
$preference = Preference::where('user_id', $user->id)->where('name', 'currencyPreference')->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']); $preference = Preference::where('user_id', $user->id)->where('name', 'currencyPreference')->first(['id', 'user_id', 'name', 'data', 'updated_at', 'created_at']);
if (null !== $preference) { if (null !== $preference->data && !is_array($preference->data)) {
return (string)$preference->data; return (string)$preference->data;
} }
return 'EUR'; return 'EUR';

View File

@ -143,10 +143,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
$user = auth()->user(); $user = auth()->user();
$route = $request->route(); $route = $request->route();
$param = $route->parameter('account'); $param = $route->parameter('account');
$accountId = 0;
if ($param instanceof Account) { if ($param instanceof Account) {
$accountId = $param->id; $accountId = $param->id;
} }
if (!($param instanceof Account)) { if (!($param instanceof Account) && !is_object($param)) {
$accountId = (int)$param; $accountId = (int)$param;
} }
/** @var Account|null $account */ /** @var Account|null $account */
@ -176,7 +177,8 @@ class GracefulNotFoundHandler extends ExceptionHandler
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$route = $request->route(); $route = $request->route();
$groupId = (int)$route->parameter('transactionGroup'); $param = $route->parameter('transactionGroup');
$groupId = !is_object($param) ? (int)$param : 0;
/** @var TransactionGroup|null $group */ /** @var TransactionGroup|null $group */
$group = $user->transactionGroups()->withTrashed()->find($groupId); $group = $user->transactionGroups()->withTrashed()->find($groupId);
@ -215,7 +217,8 @@ class GracefulNotFoundHandler extends ExceptionHandler
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
$route = $request->route(); $route = $request->route();
$attachmentId = (int)$route->parameter('attachment'); $param = $route->parameter('attachment');
$attachmentId = is_object($param) ? 0 : (int)$param;
/** @var Attachment|null $attachment */ /** @var Attachment|null $attachment */
$attachment = $user->attachments()->withTrashed()->find($attachmentId); $attachment = $user->attachments()->withTrashed()->find($attachmentId);
if (null === $attachment) { if (null === $attachment) {

View File

@ -474,7 +474,7 @@ class TransactionJournalFactory
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
{ {
app('log')->debug('Now in getCurrency()'); app('log')->debug('Now in getCurrency()');
/** @var Preference|null $preference */ /** @var TransactionCurrency|null $preference */
$preference = $this->accountRepository->getAccountCurrency($account); $preference = $this->accountRepository->getAccountCurrency($account);
if (null === $preference && null === $currency) { if (null === $preference && null === $currency) {
// return user's default: // return user's default:

View File

@ -92,6 +92,11 @@ class BudgetLimitHandler
app('log')->error($e->getMessage()); app('log')->error($e->getMessage());
$viewRange = '1M'; $viewRange = '1M';
} }
// safety catch
if(null === $viewRange || is_array($viewRange)){
$viewRange = '1M';
}
$viewRange = (string)$viewRange;
$start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange); $start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange);
$end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange); $end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange);

View File

@ -197,6 +197,9 @@ class UserEventHandler
} }
$list = app('preferences')->getForUser($user, 'login_ip_history', [])->data; $list = app('preferences')->getForUser($user, 'login_ip_history', [])->data;
if(!is_array($list)) {
$list = [];
}
/** @var array $entry */ /** @var array $entry */
foreach ($list as $index => $entry) { foreach ($list as $index => $entry) {
@ -415,7 +418,7 @@ class UserEventHandler
} }
// clean up old entries (6 months) // clean up old entries (6 months)
$carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']); $carbon = Carbon::createFromFormat('Y-m-d H:i:s', $preference[$index]['time']);
if ($carbon->diffInMonths(today()) > 6) { if (false !== $carbon && $carbon->diffInMonths(today()) > 6) {
app('log')->debug(sprintf('Entry for %s is very old, remove it.', $row['ip'])); app('log')->debug(sprintf('Entry for %s is very old, remove it.', $row['ip']));
unset($preference[$index]); unset($preference[$index]);
} }

View File

@ -161,7 +161,12 @@ class AttachmentHelper implements AttachmentHelperInterface
} }
Log::debug(sprintf('Wrote %d bytes to temp file.', $result)); Log::debug(sprintf('Wrote %d bytes to temp file.', $result));
$finfo = finfo_open(FILEINFO_MIME_TYPE); $finfo = finfo_open(FILEINFO_MIME_TYPE);
$mime = finfo_file($finfo, $path); if (false === $finfo) {
Log::error('Could not open finfo.');
fclose($resource);
return false;
}
$mime = (string) finfo_file($finfo, $path);
$allowedMime = config('firefly.allowedMimes'); $allowedMime = config('firefly.allowedMimes');
if (!in_array($mime, $allowedMime, true)) { if (!in_array($mime, $allowedMime, true)) {
Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime)); Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime));
@ -177,7 +182,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$this->uploadDisk->put($file, $content); $this->uploadDisk->put($file, $content);
// update attachment. // update attachment.
$attachment->md5 = md5_file($path); $attachment->md5 = (string) md5_file($path);
$attachment->mime = $mime; $attachment->mime = $mime;
$attachment->size = strlen($content); $attachment->size = strlen($content);
$attachment->uploaded = true; $attachment->uploaded = true;
@ -246,7 +251,7 @@ class AttachmentHelper implements AttachmentHelperInterface
$attachment = new Attachment(); // create Attachment object. $attachment = new Attachment(); // create Attachment object.
$attachment->user()->associate($user); $attachment->user()->associate($user);
$attachment->attachable()->associate($model); $attachment->attachable()->associate($model);
$attachment->md5 = md5_file($file->getRealPath()); $attachment->md5 = (string) md5_file($file->getRealPath());
$attachment->filename = $file->getClientOriginalName(); $attachment->filename = $file->getClientOriginalName();
$attachment->mime = $file->getMimeType(); $attachment->mime = $file->getMimeType();
$attachment->size = $file->getSize(); $attachment->size = $file->getSize();
@ -261,7 +266,7 @@ class AttachmentHelper implements AttachmentHelperInterface
throw new FireflyException('Cannot upload empty or non-existent file.'); throw new FireflyException('Cannot upload empty or non-existent file.');
} }
$content = $fileObject->fread($file->getSize()); $content = (string) $fileObject->fread($file->getSize());
Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize())); Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize()));
// store it without encryption. // store it without encryption.

View File

@ -226,7 +226,7 @@ trait MetaCollection
*/ */
public function excludeInternalReference(string $internalReference): GroupCollectorInterface public function excludeInternalReference(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -253,7 +253,7 @@ trait MetaCollection
*/ */
public function externalIdContains(string $externalId): GroupCollectorInterface public function externalIdContains(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -268,7 +268,7 @@ trait MetaCollection
*/ */
public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface public function externalIdDoesNotContain(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -283,7 +283,7 @@ trait MetaCollection
*/ */
public function externalIdDoesNotEnd(string $externalId): GroupCollectorInterface public function externalIdDoesNotEnd(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -298,7 +298,7 @@ trait MetaCollection
*/ */
public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface public function externalIdDoesNotStart(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -313,7 +313,7 @@ trait MetaCollection
*/ */
public function externalIdEnds(string $externalId): GroupCollectorInterface public function externalIdEnds(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -328,7 +328,7 @@ trait MetaCollection
*/ */
public function externalIdStarts(string $externalId): GroupCollectorInterface public function externalIdStarts(string $externalId): GroupCollectorInterface
{ {
$externalId = json_encode($externalId); $externalId = (string) json_encode($externalId);
$externalId = str_replace('\\', '\\\\', trim($externalId, '"')); $externalId = str_replace('\\', '\\\\', trim($externalId, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -346,7 +346,7 @@ trait MetaCollection
public function externalUrlContains(string $url): GroupCollectorInterface public function externalUrlContains(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"')); $url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url'); $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url)); $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url));
@ -362,7 +362,7 @@ trait MetaCollection
public function externalUrlDoesNotContain(string $url): GroupCollectorInterface public function externalUrlDoesNotContain(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"')); $url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url'); $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $url)); $this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s%%', $url));
@ -378,7 +378,7 @@ trait MetaCollection
public function externalUrlDoesNotEnd(string $url): GroupCollectorInterface public function externalUrlDoesNotEnd(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"')); $url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url'); $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s', $url)); $this->query->where('journal_meta.data', 'NOT LIKE', sprintf('%%%s', $url));
@ -394,7 +394,7 @@ trait MetaCollection
public function externalUrlDoesNotStart(string $url): GroupCollectorInterface public function externalUrlDoesNotStart(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', rtrim($url, '"')); $url = str_replace('\\', '\\\\', rtrim($url, '"'));
//var_dump($url); //var_dump($url);
@ -412,7 +412,7 @@ trait MetaCollection
public function externalUrlEnds(string $url): GroupCollectorInterface public function externalUrlEnds(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"')); $url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url'); $this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url)); $this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url));
@ -428,7 +428,7 @@ trait MetaCollection
public function externalUrlStarts(string $url): GroupCollectorInterface public function externalUrlStarts(string $url): GroupCollectorInterface
{ {
$this->joinMetaDataTables(); $this->joinMetaDataTables();
$url = json_encode($url); $url = (string) json_encode($url);
$url = str_replace('\\', '\\\\', rtrim($url, '"')); $url = str_replace('\\', '\\\\', rtrim($url, '"'));
//var_dump($url); //var_dump($url);
@ -487,7 +487,7 @@ trait MetaCollection
*/ */
public function internalReferenceContains(string $internalReference): GroupCollectorInterface public function internalReferenceContains(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
//var_dump($internalReference); //var_dump($internalReference);
//exit; //exit;
@ -504,7 +504,7 @@ trait MetaCollection
*/ */
public function internalReferenceDoesNotContain(string $internalReference): GroupCollectorInterface public function internalReferenceDoesNotContain(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -519,7 +519,7 @@ trait MetaCollection
*/ */
public function internalReferenceDoesNotEnd(string $internalReference): GroupCollectorInterface public function internalReferenceDoesNotEnd(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -534,7 +534,7 @@ trait MetaCollection
*/ */
public function internalReferenceDoesNotStart(string $internalReference): GroupCollectorInterface public function internalReferenceDoesNotStart(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -549,7 +549,7 @@ trait MetaCollection
*/ */
public function internalReferenceEnds(string $internalReference): GroupCollectorInterface public function internalReferenceEnds(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -564,7 +564,7 @@ trait MetaCollection
*/ */
public function internalReferenceStarts(string $internalReference): GroupCollectorInterface public function internalReferenceStarts(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();
@ -836,7 +836,7 @@ trait MetaCollection
*/ */
public function setInternalReference(string $internalReference): GroupCollectorInterface public function setInternalReference(string $internalReference): GroupCollectorInterface
{ {
$internalReference = json_encode($internalReference); $internalReference = (string) json_encode($internalReference);
$internalReference = str_replace('\\', '\\\\', trim($internalReference, '"')); $internalReference = str_replace('\\', '\\\\', trim($internalReference, '"'));
$this->joinMetaDataTables(); $this->joinMetaDataTables();

View File

@ -80,6 +80,10 @@ class FiscalHelper implements FiscalHelperInterface
$startDate = clone $date; $startDate = clone $date;
if (true === $this->useCustomFiscalYear) { if (true === $this->useCustomFiscalYear) {
$prefStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data; $prefStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data;
if(is_array($prefStartStr)) {
$prefStartStr = '01-01';
}
$prefStartStr = (string) $prefStartStr;
[$mth, $day] = explode('-', $prefStartStr); [$mth, $day] = explode('-', $prefStartStr);
$startDate->day((int)$day)->month((int)$mth); $startDate->day((int)$day)->month((int)$mth);

View File

@ -89,12 +89,12 @@ class NetWorth implements NetWorthInterface
'currency_code' => $default->code, 'currency_code' => $default->code,
'currency_name' => $default->name, 'currency_name' => $default->name,
'currency_symbol' => $default->symbol, 'currency_symbol' => $default->symbol,
'currency_decimal_places' => (int)$default->decimal_places, 'currency_decimal_places' => $default->decimal_places,
'native_id' => $default->id, 'native_id' => $default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_name' => $default->name, 'native_name' => $default->name,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
], ],
]; ];
$balances = app('steam')->balancesByAccountsConverted($accounts, $date); $balances = app('steam')->balancesByAccountsConverted($accounts, $date);
@ -125,12 +125,12 @@ class NetWorth implements NetWorthInterface
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_id' => $default->id, 'native_id' => $default->id,
'native_code' => $default->code, 'native_code' => $default->code,
'native_name' => $default->name, 'native_name' => $default->name,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
]; ];
$netWorth[$currencyId]['balance'] = bcadd($balance, $netWorth[$currencyId]['balance']); $netWorth[$currencyId]['balance'] = bcadd($balance, $netWorth[$currencyId]['balance']);

View File

@ -152,6 +152,9 @@ class CreateController extends Controller
// update preferences if necessary: // update preferences if necessary:
$frontPage = app('preferences')->get('frontPageAccounts', [])->data; $frontPage = app('preferences')->get('frontPageAccounts', [])->data;
if(!is_array($frontPage)) {
$frontPage = [];
}
if (AccountType::ASSET === $account->accountType->type) { if (AccountType::ASSET === $account->accountType->type) {
$frontPage[] = $account->id; $frontPage[] = $account->id;
app('preferences')->set('frontPageAccounts', $frontPage); app('preferences')->set('frontPageAccounts', $frontPage);

View File

@ -195,7 +195,7 @@ class EditController extends Controller
$request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name])); $request->session()->flash('success', (string)trans('firefly.updated_account', ['name' => $account->name]));
// store new attachment(s): // store new attachment(s):
/** @var array|null $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) { if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachments->saveAttachmentsForModel($account, $files); $this->attachments->saveAttachmentsForModel($account, $files);

View File

@ -207,14 +207,14 @@ class ReconcileController extends Controller
* @param Carbon $end * @param Carbon $end
* @param string $difference * @param string $difference
* *
* @return RedirectResponse|Redirector|string * @return string
* @throws DuplicateTransactionException * @throws DuplicateTransactionException
* @throws JsonException * @throws JsonException
*/ */
private function createReconciliation(Account $account, Carbon $start, Carbon $end, string $difference) private function createReconciliation(Account $account, Carbon $start, Carbon $end, string $difference): string
{ {
if (!$this->isEditableAccount($account)) { if (!$this->isEditableAccount($account)) {
return $this->redirectAccountToAccount($account); return 'not-editable';
} }
$reconciliation = $this->accountRepos->getReconciliation($account); $reconciliation = $this->accountRepos->getReconciliation($account);

View File

@ -170,6 +170,9 @@ class TwoFactorController extends Controller
private function isBackupCode(string $mfaCode): bool private function isBackupCode(string $mfaCode): bool
{ {
$list = app('preferences')->get('mfa_recovery', [])->data; $list = app('preferences')->get('mfa_recovery', [])->data;
if(!is_array($list)) {
$list = [];
}
if (in_array($mfaCode, $list, true)) { if (in_array($mfaCode, $list, true)) {
return true; return true;
} }
@ -185,6 +188,9 @@ class TwoFactorController extends Controller
private function removeFromBackupCodes(string $mfaCode): void private function removeFromBackupCodes(string $mfaCode): void
{ {
$list = app('preferences')->get('mfa_recovery', [])->data; $list = app('preferences')->get('mfa_recovery', [])->data;
if(!is_array($list)) {
$list = [];
}
$newList = array_values(array_diff($list, [$mfaCode])); $newList = array_values(array_diff($list, [$mfaCode]));
app('preferences')->set('mfa_recovery', $newList); app('preferences')->set('mfa_recovery', $newList);
} }

View File

@ -138,6 +138,11 @@ class BudgetLimitController extends Controller
} }
$start = Carbon::createFromFormat('Y-m-d', $request->get('start')); $start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end')); $end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
if (false === $start || false === $end) {
return response()->json([]);
}
$amount = (string)$request->get('amount'); $amount = (string)$request->get('amount');
$start->startOfDay(); $start->startOfDay();
$end->startOfDay(); $end->startOfDay();

View File

@ -125,6 +125,7 @@ class CreateController extends Controller
app('preferences')->mark(); app('preferences')->mark();
// store attachment(s): // store attachment(s):
/** @var array|null $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) { if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachments->saveAttachmentsForModel($budget, $files); $this->attachments->saveAttachmentsForModel($budget, $files);

View File

@ -102,6 +102,10 @@ class EditController extends Controller
]; ];
if (null !== $autoBudget) { if (null !== $autoBudget) {
$amount = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount; $amount = $hasOldInput ? $request->old('auto_budget_amount') : $autoBudget->amount;
if (is_array($amount)) {
$amount = '0';
}
$amount = (string)$amount;
$preFilled['auto_budget_amount'] = app('steam')->bcround($amount, $autoBudget->transactionCurrency->decimal_places); $preFilled['auto_budget_amount'] = app('steam')->bcround($amount, $autoBudget->transactionCurrency->decimal_places);
} }
@ -135,6 +139,7 @@ class EditController extends Controller
$redirect = redirect($this->getPreviousUrl('budgets.edit.url')); $redirect = redirect($this->getPreviousUrl('budgets.edit.url'));
// store new attachment(s): // store new attachment(s):
/** @var array|null $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) { if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachments->saveAttachmentsForModel($budget, $files); $this->attachments->saveAttachmentsForModel($budget, $files);

View File

@ -98,6 +98,7 @@ class CreateController extends Controller
app('preferences')->mark(); app('preferences')->mark();
// store attachment(s): // store attachment(s):
/** @var array|null $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) { if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachments->saveAttachmentsForModel($category, $files); $this->attachments->saveAttachmentsForModel($category, $files);

View File

@ -105,6 +105,7 @@ class EditController extends Controller
app('preferences')->mark(); app('preferences')->mark();
// store new attachment(s): // store new attachment(s):
/** @var array|null $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null; $files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) { if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachments->saveAttachmentsForModel($category, $files); $this->attachments->saveAttachmentsForModel($category, $files);

View File

@ -336,12 +336,13 @@ class AccountController extends Controller
$defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray(); $defaultSet = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray();
app('log')->debug('Default set is ', $defaultSet); app('log')->debug('Default set is ', $defaultSet);
$frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $frontPage = app('preferences')->get('frontPageAccounts', $defaultSet);
app('log')->debug('Frontpage preference set is ', $frontPage->data); $frontPageArray = !is_array($frontPage->data) ? [] : $frontPage->data;
if (0 === count($frontPage->data)) { app('log')->debug('Frontpage preference set is ', $frontPageArray);
if (0 === count($frontPageArray)) {
app('preferences')->set('frontPageAccounts', $defaultSet); app('preferences')->set('frontPageAccounts', $defaultSet);
app('log')->debug('frontpage set is empty!'); app('log')->debug('frontpage set is empty!');
} }
$accounts = $repository->getAccountsById($frontPage->data); $accounts = $repository->getAccountsById($frontPageArray);
return response()->json($this->accountBalanceChart($accounts, $start, $end)); return response()->json($this->accountBalanceChart($accounts, $start, $end));
} }

View File

@ -74,8 +74,8 @@ abstract class Controller extends BaseController
app('view')->share('logoutUrl', $logoutUrl); app('view')->share('logoutUrl', $logoutUrl);
// upload size // upload size
$maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); $maxFileSize = app('steam')->phpBytes((string)ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); $maxPostSize = app('steam')->phpBytes((string)ini_get('post_max_size'));
$uploadSize = min($maxFileSize, $maxPostSize); $uploadSize = min($maxFileSize, $maxPostSize);
app('view')->share('uploadSize', $uploadSize); app('view')->share('uploadSize', $uploadSize);

View File

@ -141,7 +141,7 @@ class DebugController extends Controller
} }
if ('' !== $logContent) { if ('' !== $logContent) {
// last few lines // last few lines
$logContent = 'Truncated from this point <----|' . substr($logContent, -16384); $logContent = 'Truncated from this point <----|' . substr((string)$logContent, -16384);
} }
return view('debug', compact('table', 'now', 'logContent')); return view('debug', compact('table', 'now', 'logContent'));
@ -166,8 +166,8 @@ class DebugController extends Controller
*/ */
private function getSystemInformation(): array private function getSystemInformation(): array
{ {
$maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); $maxFileSize = app('steam')->phpBytes((string)ini_get('upload_max_filesize'));
$maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); $maxPostSize = app('steam')->phpBytes((string)ini_get('post_max_size'));
$drivers = DB::availableDrivers(); $drivers = DB::availableDrivers();
$currentDriver = DB::getDriverName(); $currentDriver = DB::getDriverName();
return [ return [
@ -199,7 +199,7 @@ class DebugController extends Controller
]; ];
try { try {
if (file_exists('/var/www/counter-main.txt')) { if (file_exists('/var/www/counter-main.txt')) {
$return['build'] = trim(file_get_contents('/var/www/counter-main.txt')); $return['build'] = trim((string)file_get_contents('/var/www/counter-main.txt'));
} }
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
app('log')->debug('Could not check build counter, but thats ok.'); app('log')->debug('Could not check build counter, but thats ok.');
@ -207,7 +207,7 @@ class DebugController extends Controller
} }
try { try {
if (file_exists('/var/www/build-date-main.txt')) { if (file_exists('/var/www/build-date-main.txt')) {
$return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt')); $return['build_date'] = trim((string)file_get_contents('/var/www/build-date-main.txt'));
} }
} catch (Exception $e) { // @phpstan-ignore-line } catch (Exception $e) { // @phpstan-ignore-line
app('log')->debug('Could not check build date, but thats ok.'); app('log')->debug('Could not check build date, but thats ok.');
@ -236,7 +236,7 @@ class DebugController extends Controller
if ($lastTime > 0) { if ($lastTime > 0) {
$carbon = Carbon::createFromTimestamp($lastTime); $carbon = Carbon::createFromTimestamp($lastTime);
$lastCronjob = $carbon->format('Y-m-d H:i:s'); $lastCronjob = $carbon->format('Y-m-d H:i:s');
$lastCronjobAgo = $carbon->locale('en')->diffForHumans(); $lastCronjobAgo = $carbon->locale('en')->diffForHumans(); // @phpstan-ignore-line
} }
return [ return [
@ -279,7 +279,7 @@ class DebugController extends Controller
$result = setlocale(LC_ALL, $code); $result = setlocale(LC_ALL, $code);
$localeAttempts[$code] = $result === $code; $localeAttempts[$code] = $result === $code;
} }
setlocale(LC_ALL, $original); setlocale(LC_ALL, (string) $original);
return [ return [
'user_id' => auth()->user()->id, 'user_id' => auth()->user()->id,

View File

@ -25,7 +25,9 @@ namespace FireflyIII\Models;
use Carbon\Carbon; use Carbon\Carbon;
use Eloquent; use Eloquent;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@ -33,7 +35,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Query\Builder; use Illuminate\Database\Query\Builder;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
/** /**
* FireflyIII\Models\TransactionCurrency * FireflyIII\Models\TransactionCurrency
* *
@ -47,7 +49,7 @@ use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
* @property string $code * @property string $code
* @property string $name * @property string $name
* @property string $symbol * @property string $symbol
* @property int|string $decimal_places * @property int $decimal_places
* @property-read Collection|BudgetLimit[] $budgetLimits * @property-read Collection|BudgetLimit[] $budgetLimits
* @property-read int|null $budget_limits_count * @property-read int|null $budget_limits_count
* @property-read Collection|TransactionJournal[] $transactionJournals * @property-read Collection|TransactionJournal[] $transactionJournals
@ -170,4 +172,14 @@ class TransactionCurrency extends Model
{ {
return $this->belongsToMany(User::class)->withTimestamps()->withPivot('user_default'); return $this->belongsToMany(User::class)->withTimestamps()->withPivot('user_default');
} }
/**
* @return Attribute
*/
protected function decimalPlaces(): Attribute
{
return Attribute::make(
get: static fn($value) => (int)$value,
);
}
} }

View File

@ -88,12 +88,12 @@ class BillRepository implements BillRepositoryInterface
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_id' => (string)$default->id, 'native_id' => (string)$default->id,
'native_name' => $default->name, 'native_name' => $default->name,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_code' => $default->code, 'native_code' => $default->code,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
'sum' => '0', 'sum' => '0',
'native_sum' => '0', 'native_sum' => '0',
]; ];
@ -162,12 +162,12 @@ class BillRepository implements BillRepositoryInterface
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_id' => (string)$default->id, 'native_id' => (string)$default->id,
'native_name' => $default->name, 'native_name' => $default->name,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_code' => $default->code, 'native_code' => $default->code,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
'sum' => '0', 'sum' => '0',
'native_sum' => '0', 'native_sum' => '0',
]; ];

View File

@ -64,7 +64,7 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
'native_code' => $default->code, 'native_code' => $default->code,
'native_symbol' => $default->symbol, 'native_symbol' => $default->symbol,
'native_name' => $default->name, 'native_name' => $default->name,
'native_decimal_places' => (int)$default->decimal_places, 'native_decimal_places' => $default->decimal_places,
'amount' => '0', 'amount' => '0',
'native_amount' => '0', 'native_amount' => '0',
]; ];

View File

@ -200,7 +200,7 @@ class TransactionGroupTwig extends AbstractExtension
if ($type === TransactionType::TRANSFER) { if ($type === TransactionType::TRANSFER) {
$colored = false; $colored = false;
} }
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) { if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result); $result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
} }
@ -243,7 +243,7 @@ class TransactionGroupTwig extends AbstractExtension
if ($type === TransactionType::TRANSFER) { if ($type === TransactionType::TRANSFER) {
$colored = false; $colored = false;
} }
$result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); $result = app('amount')->formatFlat($currency->symbol, $currency->decimal_places, $amount, $colored);
if ($type === TransactionType::TRANSFER) { if ($type === TransactionType::TRANSFER) {
$result = sprintf('<span class="text-info money-transfer">%s</span>', $result); $result = sprintf('<span class="text-info money-transfer">%s</span>', $result);
} }

View File

@ -68,7 +68,7 @@ class AvailableBudgetTransformer extends AbstractTransformer
'currency_id' => (string)$currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'amount' => app('steam')->bcround($availableBudget->amount, $currency->decimal_places), 'amount' => app('steam')->bcround($availableBudget->amount, $currency->decimal_places),
'start' => $availableBudget->start_date->toAtomString(), 'start' => $availableBudget->start_date->toAtomString(),
'end' => $availableBudget->end_date->endOfDay()->toAtomString(), 'end' => $availableBudget->end_date->endOfDay()->toAtomString(),

View File

@ -128,7 +128,7 @@ class BillTransformer extends AbstractTransformer
'currency_id' => (string)$bill->transaction_currency_id, 'currency_id' => (string)$bill->transaction_currency_id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'name' => $bill->name, 'name' => $bill->name,
'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places), 'amount_min' => app('steam')->bcround($bill->amount_min, $currency->decimal_places),
'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places), 'amount_max' => app('steam')->bcround($bill->amount_max, $currency->decimal_places),

View File

@ -48,7 +48,7 @@ class CurrencyTransformer extends AbstractTransformer
'name' => $currency->name, 'name' => $currency->name,
'code' => $currency->code, 'code' => $currency->code,
'symbol' => $currency->symbol, 'symbol' => $currency->symbol,
'decimal_places' => (int)$currency->decimal_places, 'decimal_places' => $currency->decimal_places,
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',

View File

@ -85,7 +85,7 @@ class PiggyBankEventTransformer extends AbstractTransformer
'currency_id' => (string)$currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'transaction_journal_id' => null !== $journalId ? (string)$journalId : null, 'transaction_journal_id' => null !== $journalId ? (string)$journalId : null,
'transaction_group_id' => null !== $groupId ? (string)$groupId : null, 'transaction_group_id' => null !== $groupId ? (string)$groupId : null,
'links' => [ 'links' => [

View File

@ -112,7 +112,7 @@ class PiggyBankTransformer extends AbstractTransformer
'currency_id' => (string)$currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'target_amount' => $targetAmount, 'target_amount' => $targetAmount,
'percentage' => $percentage, 'percentage' => $percentage,
'current_amount' => $currentAmount, 'current_amount' => $currentAmount,

View File

@ -415,7 +415,7 @@ class TransactionGroupTransformer extends AbstractTransformer
'currency_id' => $currency->id, 'currency_id' => $currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'foreign_currency_id' => $foreignCurrency['id'], 'foreign_currency_id' => $foreignCurrency['id'],
'foreign_currency_code' => $foreignCurrency['code'], 'foreign_currency_code' => $foreignCurrency['code'],
@ -592,7 +592,7 @@ class TransactionGroupTransformer extends AbstractTransformer
$array['id'] = $currency->id; $array['id'] = $currency->id;
$array['code'] = $currency->code; $array['code'] = $currency->code;
$array['symbol'] = $currency->symbol; $array['symbol'] = $currency->symbol;
$array['decimal_places'] = (int)$currency->decimal_places; $array['decimal_places'] = $currency->decimal_places;
return $array; return $array;
} }

View File

@ -144,7 +144,7 @@ class AccountTransformer extends AbstractTransformer
'currency_id' => (string)$currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$this->default->id, 'native_currency_id' => (string)$this->default->id,
'native_currency_code' => $this->default->code, 'native_currency_code' => $this->default->code,

View File

@ -207,7 +207,7 @@ class BillTransformer extends AbstractTransformer
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_name' => $currency->name, 'currency_name' => $currency->name,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => $this->default->id, 'native_currency_id' => $this->default->id,
'native_currency_code' => $this->default->code, 'native_currency_code' => $this->default->code,
'native_currency_name' => $this->default->name, 'native_currency_name' => $this->default->name,

View File

@ -56,7 +56,7 @@ class CurrencyTransformer extends AbstractTransformer
'name' => $currency->name, 'name' => $currency->name,
'code' => $currency->code, 'code' => $currency->code,
'symbol' => $currency->symbol, 'symbol' => $currency->symbol,
'decimal_places' => (int)$currency->decimal_places, 'decimal_places' => $currency->decimal_places,
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',

View File

@ -208,7 +208,7 @@ class PiggyBankTransformer extends AbstractTransformer
'currency_id' => (string)$currency->id, 'currency_id' => (string)$currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => (int)$currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'native_currency_id' => (string)$this->default->id, 'native_currency_id' => (string)$this->default->id,
'native_currency_code' => $this->default->code, 'native_currency_code' => $this->default->code,
'native_currency_symbol' => $this->default->symbol, 'native_currency_symbol' => $this->default->symbol,

View File

@ -14,6 +14,7 @@
stopOnFailure="true"> stopOnFailure="true">
<php> <php>
<env name="APP_ENV" value="testing"/> <env name="APP_ENV" value="testing"/>
<env name="APP_LOG_ENV" value="notice"/>
<ini name="xdebug.mode" value="coverage"/> <ini name="xdebug.mode" value="coverage"/>
</php> </php>
<testsuites> <testsuites>

View File

@ -46,8 +46,9 @@
<tr> <tr>
<td>{{ 'next_expected_match'|_ }}</td> <td>{{ 'next_expected_match'|_ }}</td>
<td> <td>
{% if object.data.next_expected_match|length > 0 %}
{{ formatDate(object.data.next_expected_match, monthAndDayFormat) }} {% if object.data.pay_dates|length > 0 %}
{{ formatDate(object.data.pay_dates[0], monthAndDayFormat) }}
{% else %} {% else %}
{{ 'unknown'|_ }} {{ 'unknown'|_ }}
{% endif %} {% endif %}

View File

@ -45,7 +45,14 @@ class BillDateCalculatorTest extends TestCase
// Carbon $earliest, Carbon $latest, Carbon $billStart, string $period, int $skip, ?Carbon $lastPaid // Carbon $earliest, Carbon $latest, Carbon $billStart, string $period, int $skip, ?Carbon $lastPaid
return [ return [
// basic monthly bill. // basic monthly bill.
'1M' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => null, 'expected' => ['2023-11-01']], '1Ma' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => null, 'expected' => ['2023-11-01']],
// already paid on the first, expect it next month.
'1Mb' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => Carbon::parse('2023-11-01'), 'expected' => ['2023-12-01']],
// already paid on the 12th, expect it next month.
'1Mc' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2023-01-01'), 'period' => 'monthly', 'skip' => 0, 'lastPaid' => Carbon::parse('2023-11-12'), 'expected' => ['2023-12-01']],
// yearly not due this month. Should jump to next year.
'1Ya' => ['earliest' => Carbon::parse('2023-11-01'), 'latest' => Carbon::parse('2023-11-30'), 'billStart' => Carbon::parse('2021-05-01'), 'period' => 'yearly', 'skip' => 0, 'lastPaid' => Carbon::parse('2023-05-02'), 'expected' => ['2024-05-01']],
]; ];
} }