Clean up some code.

This commit is contained in:
James Cole 2024-12-14 05:45:54 +01:00
parent b930ad4da7
commit 7d8d773f8f
No known key found for this signature in database
GPG Key ID: B49A324B7EAD6D80
78 changed files with 2760 additions and 2674 deletions

View File

@ -66,9 +66,9 @@ class PiggyBankController extends Controller
*/ */
public function piggyBanks(AutocompleteRequest $request): JsonResponse public function piggyBanks(AutocompleteRequest $request): JsonResponse
{ {
$data = $request->getData(); $data = $request->getData();
$piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit')); $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit'));
$response = []; $response = [];
/** @var PiggyBank $piggy */ /** @var PiggyBank $piggy */
foreach ($piggies as $piggy) { foreach ($piggies as $piggy) {
@ -96,9 +96,9 @@ class PiggyBankController extends Controller
*/ */
public function piggyBanksWithBalance(AutocompleteRequest $request): JsonResponse public function piggyBanksWithBalance(AutocompleteRequest $request): JsonResponse
{ {
$data = $request->getData(); $data = $request->getData();
$piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit')); $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit'));
$response = []; $response = [];
/** @var PiggyBank $piggy */ /** @var PiggyBank $piggy */
foreach ($piggies as $piggy) { foreach ($piggies as $piggy) {

View File

@ -63,8 +63,8 @@ class DestroyController extends Controller
*/ */
public function destroy(DestroyRequest $request): JsonResponse public function destroy(DestroyRequest $request): JsonResponse
{ {
$objects = $request->getObjects(); $objects = $request->getObjects();
$this->unused = $request->boolean('unused', false); $this->unused = $request->boolean('unused', false);
$allExceptAssets = [AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::RECONCILIATION, AccountType::REVENUE]; $allExceptAssets = [AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::RECONCILIATION, AccountType::REVENUE];
$all = [AccountType::ASSET, AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEBT, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::RECONCILIATION]; $all = [AccountType::ASSET, AccountType::BENEFICIARY, AccountType::CASH, AccountType::CREDITCARD, AccountType::DEBT, AccountType::DEFAULT, AccountType::EXPENSE, AccountType::IMPORT, AccountType::INITIAL_BALANCE, AccountType::LIABILITY_CREDIT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::RECONCILIATION];
@ -101,11 +101,11 @@ class DestroyController extends Controller
private function destroyBudgets(): void private function destroyBudgets(): void
{ {
/** @var AvailableBudgetRepositoryInterface $abRepository */ /** @var AvailableBudgetRepositoryInterface $abRepository */
$abRepository = app(AvailableBudgetRepositoryInterface::class); $abRepository = app(AvailableBudgetRepositoryInterface::class);
$abRepository->destroyAll(); $abRepository->destroyAll();
/** @var BudgetLimitRepositoryInterface $blRepository */ /** @var BudgetLimitRepositoryInterface $blRepository */
$blRepository = app(BudgetLimitRepositoryInterface::class); $blRepository = app(BudgetLimitRepositoryInterface::class);
$blRepository->destroyAll(); $blRepository->destroyAll();
/** @var BudgetRepositoryInterface $budgetRepository */ /** @var BudgetRepositoryInterface $budgetRepository */

View File

@ -53,7 +53,7 @@ class PurgeController extends Controller
public function purge(): JsonResponse public function purge(): JsonResponse
{ {
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
// some manual code, too lazy to call all repositories. // some manual code, too lazy to call all repositories.
@ -67,14 +67,14 @@ class PurgeController extends Controller
$repository = app(PiggyBankRepositoryInterface::class); $repository = app(PiggyBankRepositoryInterface::class);
$repository->setUser($user); $repository->setUser($user);
$repository->purgeAll(); $repository->purgeAll();
// $set = PiggyBank::leftJoin('accounts', 'accounts.id', 'piggy_banks.account_id') // $set = PiggyBank::leftJoin('accounts', 'accounts.id', 'piggy_banks.account_id')
// ->where('accounts.user_id', $user->id)->onlyTrashed()->get(['piggy_banks.*']) // ->where('accounts.user_id', $user->id)->onlyTrashed()->get(['piggy_banks.*'])
// ; // ;
// //
// /** @var PiggyBank $piggy */ // /** @var PiggyBank $piggy */
// foreach ($set as $piggy) { // foreach ($set as $piggy) {
// $piggy->forceDelete(); // $piggy->forceDelete();
// } // }
// rule group // rule group
RuleGroup::whereUserId($user->id)->onlyTrashed()->forceDelete(); RuleGroup::whereUserId($user->id)->onlyTrashed()->forceDelete();

View File

@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Insight\GenericRequest; use FireflyIII\Api\V1\Requests\Insight\GenericRequest;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Insight\GenericRequest; use FireflyIII\Api\V1\Requests\Insight\GenericRequest;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**

View File

@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Insight\GenericRequest; use FireflyIII\Api\V1\Requests\Insight\GenericRequest;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Insight\GenericRequest; use FireflyIII\Api\V1\Requests\Insight\GenericRequest;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
/** /**

View File

@ -28,7 +28,6 @@ use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Insight\GenericRequest; use FireflyIII\Api\V1\Requests\Insight\GenericRequest;
use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;

View File

@ -72,14 +72,14 @@ class StoreController extends Controller
$data['notes'] = $data['notes']; $data['notes'] = $data['notes'];
$data['budget_id'] = $budget->id; $data['budget_id'] = $budget->id;
$budgetLimit = $this->blRepository->store($data); $budgetLimit = $this->blRepository->store($data);
$manager = $this->getManager(); $manager = $this->getManager();
/** @var BudgetLimitTransformer $transformer */ /** @var BudgetLimitTransformer $transformer */
$transformer = app(BudgetLimitTransformer::class); $transformer = app(BudgetLimitTransformer::class);
$transformer->setParameters($this->parameters); $transformer->setParameters($this->parameters);
$resource = new Item($budgetLimit, $transformer, 'budget_limits'); $resource = new Item($budgetLimit, $transformer, 'budget_limits');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
} }

View File

@ -32,7 +32,6 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface; use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;

View File

@ -53,10 +53,11 @@ class UpdateRequest extends FormRequest
'currency_code' => ['currency_code', 'convertString'], 'currency_code' => ['currency_code', 'convertString'],
'notes' => ['notes', 'stringWithNewlines'], 'notes' => ['notes', 'stringWithNewlines'],
]; ];
if(false === $this->has('notes')) { if (false === $this->has('notes')) {
// ignore notes, not submitted. // ignore notes, not submitted.
unset($fields['notes']); unset($fields['notes']);
} }
return $this->getAllData($fields); return $this->getAllData($fields);
} }

View File

@ -27,7 +27,6 @@ namespace FireflyIII\Api\V1\Requests\Models\PiggyBank;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Rules\IsValidPositiveAmount;
use FireflyIII\Rules\IsValidZeroOrMoreAmount; use FireflyIII\Rules\IsValidZeroOrMoreAmount;
use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes; use FireflyIII\Support\Request\ConvertsDataTypes;
@ -96,9 +95,9 @@ class StoreRequest extends FormRequest
$validator->after( $validator->after(
function (Validator $validator): void { function (Validator $validator): void {
// validate start before end only if both are there. // validate start before end only if both are there.
$data = $validator->getData(); $data = $validator->getData();
$currency = $this->getCurrencyFromData($data); $currency = $this->getCurrencyFromData($data);
$targetAmount = (string) ($data['target_amount'] ?? '0'); $targetAmount = (string) ($data['target_amount'] ?? '0');
$currentAmount = '0'; $currentAmount = '0';
if (array_key_exists('accounts', $data) && is_array($data['accounts'])) { if (array_key_exists('accounts', $data) && is_array($data['accounts'])) {
$repository = app(AccountRepositoryInterface::class); $repository = app(AccountRepositoryInterface::class);
@ -110,18 +109,18 @@ class StoreRequest extends FormRequest
// check currency here. // check currency here.
$accountCurrency = $repository->getAccountCurrency($account); $accountCurrency = $repository->getAccountCurrency($account);
$isMultiCurrency = $repository->getMetaValue($account, 'is_multi_currency'); $isMultiCurrency = $repository->getMetaValue($account, 'is_multi_currency');
$currentAmount = bcadd($currentAmount, (string)($array['current_amount'] ?? '0')); $currentAmount = bcadd($currentAmount, (string)($array['current_amount'] ?? '0'));
if ($accountCurrency->id !== $currency->id && 'true' !== $isMultiCurrency) { if ($accountCurrency->id !== $currency->id && 'true' !== $isMultiCurrency) {
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_currency')); $validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_currency'));
} }
$type = $account->accountType->type; $type = $account->accountType->type;
if (!in_array($type, $types, true)) { if (!in_array($type, $types, true)) {
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_type')); $validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_type'));
} }
} }
} }
} }
if(bccomp($targetAmount, $currentAmount) === -1 && bccomp($targetAmount, '0') === 1) { if (-1 === bccomp($targetAmount, $currentAmount) && 1 === bccomp($targetAmount, '0')) {
$validator->errors()->add('target_amount', trans('validation.current_amount_too_much')); $validator->errors()->add('target_amount', trans('validation.current_amount_too_much'));
} }
} }
@ -146,6 +145,7 @@ class StoreRequest extends FormRequest
'current_amount' => $this->clearString((string) ($entry['current_amount'] ?? '0')), 'current_amount' => $this->clearString((string) ($entry['current_amount'] ?? '0')),
]; ];
} }
return $return; return $return;
} }
@ -163,6 +163,7 @@ class StoreRequest extends FormRequest
return $currency; return $currency;
} }
} }
throw new FireflyException('Unexpected empty currency.'); throw new FireflyException('Unexpected empty currency.');
} }
} }

View File

@ -30,7 +30,6 @@ use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Chart\ChartData; use FireflyIII\Support\Chart\ChartData;
use FireflyIII\Support\Http\Api\AccountBalanceGrouped; use FireflyIII\Support\Http\Api\AccountBalanceGrouped;

View File

@ -31,7 +31,6 @@ use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\UserGroups\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData; use FireflyIII\Support\Http\Api\CleansChartData;

View File

@ -33,7 +33,6 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Helpers\Report\NetWorthInterface;
use FireflyIII\Models\Account; use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType; use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface; use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface; use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface;

View File

@ -265,8 +265,8 @@ class ApplyRules extends Command
private function verifyInputDates(): void private function verifyInputDates(): void
{ {
// parse start date. // parse start date.
$inputStart = today(config('app.timezone'))->startOfMonth(); $inputStart = today(config('app.timezone'))->startOfMonth();
$startString = $this->option('start_date'); $startString = $this->option('start_date');
if (null === $startString) { if (null === $startString) {
/** @var JournalRepositoryInterface $repository */ /** @var JournalRepositoryInterface $repository */
$repository = app(JournalRepositoryInterface::class); $repository = app(JournalRepositoryInterface::class);
@ -281,8 +281,8 @@ class ApplyRules extends Command
} }
// parse end date // parse end date
$inputEnd = today(config('app.timezone')); $inputEnd = today(config('app.timezone'));
$endString = $this->option('end_date'); $endString = $this->option('end_date');
if (null !== $endString && '' !== $endString) { if (null !== $endString && '' !== $endString) {
$inputEnd = Carbon::createFromFormat('Y-m-d', $endString); $inputEnd = Carbon::createFromFormat('Y-m-d', $endString);
} }
@ -297,7 +297,7 @@ class ApplyRules extends Command
} }
$this->start_date = $inputStart; $this->start_date = $inputStart;
$this->endDate = $inputEnd; $this->endDate = $inputEnd;
} }
private function grabAllRules(): void private function grabAllRules(): void

View File

@ -1,4 +1,6 @@
<?php <?php
declare(strict_types=1);
/* /*
* UpgradeMultiPiggyBanks.php * UpgradeMultiPiggyBanks.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -34,17 +36,15 @@ class UpgradeMultiPiggyBanks extends Command
public const string CONFIG_NAME = '620_make_multi_piggies'; public const string CONFIG_NAME = '620_make_multi_piggies';
protected $description = 'Upgrade piggybanks so they can use multiple accounts.'; protected $description = 'Upgrade piggybanks so they can use multiple accounts.';
protected $signature = 'firefly-iii:upgrade-multi-piggies {--F|force : Force the execution of this command.}'; protected $signature = 'firefly-iii:upgrade-multi-piggies {--F|force : Force the execution of this command.}';
private PiggyBankRepositoryInterface $repository; private PiggyBankRepositoryInterface $repository;
private AccountRepositoryInterface $accountRepository; private AccountRepositoryInterface $accountRepository;
/** /**
* Execute the console command. * Execute the console command.
*
* @return int
*/ */
public function handle(): int public function handle(): int
{ {
@ -61,9 +61,6 @@ class UpgradeMultiPiggyBanks extends Command
return 0; return 0;
} }
/**
* @return bool
*/
private function isExecuted(): bool private function isExecuted(): bool
{ {
$configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false);
@ -74,10 +71,6 @@ class UpgradeMultiPiggyBanks extends Command
return false; return false;
} }
/**
*
*/
private function markAsExecuted(): void private function markAsExecuted(): void
{ {
app('fireflyconfig')->set(self::CONFIG_NAME, true); app('fireflyconfig')->set(self::CONFIG_NAME, true);
@ -89,6 +82,7 @@ class UpgradeMultiPiggyBanks extends Command
$this->accountRepository = app(AccountRepositoryInterface::class); $this->accountRepository = app(AccountRepositoryInterface::class);
$set = PiggyBank::whereNotNull('account_id')->get(); $set = PiggyBank::whereNotNull('account_id')->get();
Log::debug(sprintf('Will update %d piggy banks(s).', $set->count())); Log::debug(sprintf('Will update %d piggy banks(s).', $set->count()));
/** @var PiggyBank $piggyBank */ /** @var PiggyBank $piggyBank */
foreach ($set as $piggyBank) { foreach ($set as $piggyBank) {
$this->upgradePiggyBank($piggyBank); $this->upgradePiggyBank($piggyBank);
@ -99,8 +93,8 @@ class UpgradeMultiPiggyBanks extends Command
{ {
$this->repository->setUser($piggyBank->account->user); $this->repository->setUser($piggyBank->account->user);
$this->accountRepository->setUser($piggyBank->account->user); $this->accountRepository->setUser($piggyBank->account->user);
$repetition = $this->repository->getRepetition($piggyBank); $repetition = $this->repository->getRepetition($piggyBank);
$currency = $this->accountRepository->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrencyByUserGroup($piggyBank->account->user->userGroup); $currency = $this->accountRepository->getAccountCurrency($piggyBank->account) ?? app('amount')->getDefaultCurrencyByUserGroup($piggyBank->account->user->userGroup);
// update piggy bank to have a currency. // update piggy bank to have a currency.
$piggyBank->transaction_currency_id = $currency->id; $piggyBank->transaction_currency_id = $currency->id;
@ -108,7 +102,7 @@ class UpgradeMultiPiggyBanks extends Command
// store current amount in account association. // store current amount in account association.
$piggyBank->accounts()->sync([$piggyBank->account->id => ['current_amount' => $repetition->current_amount]]); $piggyBank->accounts()->sync([$piggyBank->account->id => ['current_amount' => $repetition->current_amount]]);
$piggyBank->account_id = null; $piggyBank->account_id = null;
$piggyBank->save(); $piggyBank->save();
// remove all repetitions (no longer used) // remove all repetitions (no longer used)

View File

@ -43,7 +43,7 @@ class RegisteredUser extends Event
*/ */
public function __construct(OwnerNotifiable $owner, User $user) public function __construct(OwnerNotifiable $owner, User $user)
{ {
$this->user = $user; $this->user = $user;
$this->owner = $owner; $this->owner = $owner;
} }
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* UnknownUserAttemptedLogin.php * UnknownUserAttemptedLogin.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -24,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Events\Security; namespace FireflyIII\Events\Security;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class UnknownUserAttemptedLogin class UnknownUserAttemptedLogin
{ {

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* TestEmailChannel.php * TestEmailChannel.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.

View File

@ -43,7 +43,8 @@ use Illuminate\Support\Facades\Notification;
*/ */
class AdminEventHandler class AdminEventHandler
{ {
public function sendLoginAttemptNotification(UnknownUserAttemptedLogin $event): void { public function sendLoginAttemptNotification(UnknownUserAttemptedLogin $event): void
{
try { try {
$owner = new OwnerNotifiable(); $owner = new OwnerNotifiable();
Notification::send($owner, new UnknownUserLoginAttempt($event->address)); Notification::send($owner, new UnknownUserLoginAttempt($event->address));
@ -64,13 +65,13 @@ class AdminEventHandler
} }
} }
public function sendInvitationNotification(InvitationCreated $event): void public function sendInvitationNotification(InvitationCreated $event): void
{ {
$sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data; $sendMail = app('fireflyconfig')->get('notification_invite_created', true)->data;
if (false === $sendMail) { if (false === $sendMail) {
return; return;
} }
try { try {
$owner = new OwnerNotifiable(); $owner = new OwnerNotifiable();
Notification::send($owner, new UserInvitation($owner, $event->invitee)); Notification::send($owner, new UserInvitation($owner, $event->invitee));
@ -131,18 +132,27 @@ class AdminEventHandler
switch ($event->channel) { switch ($event->channel) {
case 'email': case 'email':
$class = TestNotificationEmail::class; $class = TestNotificationEmail::class;
break; break;
case 'slack': case 'slack':
$class = TestNotificationSlack::class; $class = TestNotificationSlack::class;
break; break;
case 'ntfy': case 'ntfy':
$class = TestNotificationNtfy::class; $class = TestNotificationNtfy::class;
break; break;
case 'pushover': case 'pushover':
$class = TestNotificationPushover::class; $class = TestNotificationPushover::class;
break; break;
default: default:
app('log')->error(sprintf('Unknown channel "%s" in sendTestNotification method.', $event->channel)); app('log')->error(sprintf('Unknown channel "%s" in sendTestNotification method.', $event->channel));
return; return;
} }
Log::debug(sprintf('Will send %s as a notification.', $class)); Log::debug(sprintf('Will send %s as a notification.', $class));

View File

@ -82,8 +82,8 @@ class UserEventHandler
$repository = app(UserRepositoryInterface::class); $repository = app(UserRepositoryInterface::class);
/** @var User $user */ /** @var User $user */
$user = $event->user; $user = $event->user;
$count = $repository->count(); $count = $repository->count();
// only act when there is 1 user in the system and he has no admin rights. // only act when there is 1 user in the system and he has no admin rights.
if (1 === $count && !$repository->hasRole($user, 'owner')) { if (1 === $count && !$repository->hasRole($user, 'owner')) {
@ -115,13 +115,13 @@ class UserEventHandler
*/ */
public function createGroupMembership(RegisteredUser $event): void public function createGroupMembership(RegisteredUser $event): void
{ {
$user = $event->user; $user = $event->user;
$groupExists = true; $groupExists = true;
$groupTitle = $user->email; $groupTitle = $user->email;
$index = 1; $index = 1;
/** @var UserGroup $group */ /** @var UserGroup $group */
$group = null; $group = null;
// create a new group. // create a new group.
while (true === $groupExists) { // @phpstan-ignore-line while (true === $groupExists) { // @phpstan-ignore-line
@ -131,7 +131,7 @@ class UserEventHandler
break; break;
} }
$groupTitle = sprintf('%s-%d', $user->email, $index); $groupTitle = sprintf('%s-%d', $user->email, $index);
++$index; ++$index;
if ($index > 99) { if ($index > 99) {
throw new FireflyException('Email address can no longer be used for registrations.'); throw new FireflyException('Email address can no longer be used for registrations.');
@ -139,7 +139,7 @@ class UserEventHandler
} }
/** @var null|UserRole $role */ /** @var null|UserRole $role */
$role = UserRole::where('title', UserRoleEnum::OWNER->value)->first(); $role = UserRole::where('title', UserRoleEnum::OWNER->value)->first();
if (null === $role) { if (null === $role) {
throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?'); throw new FireflyException('The user role is unexpectedly empty. Did you run all migrations?');
} }
@ -165,7 +165,7 @@ class UserEventHandler
$repository = app(UserRepositoryInterface::class); $repository = app(UserRepositoryInterface::class);
/** @var User $user */ /** @var User $user */
$user = $event->user; $user = $event->user;
if ($repository->hasRole($user, 'demo')) { if ($repository->hasRole($user, 'demo')) {
// set user back to English. // set user back to English.
app('preferences')->setForUser($user, 'language', 'en_US'); app('preferences')->setForUser($user, 'language', 'en_US');
@ -186,7 +186,7 @@ class UserEventHandler
return; // do not email demo user. return; // do not email demo user.
} }
$list = app('preferences')->getForUser($user, 'login_ip_history', [])->data; $list = app('preferences')->getForUser($user, 'login_ip_history', [])->data;
if (!is_array($list)) { if (!is_array($list)) {
$list = []; $list = [];
} }
@ -223,6 +223,7 @@ class UserEventHandler
$sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data; $sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data;
if ($sendMail) { if ($sendMail) {
$owner = $event->owner; $owner = $event->owner;
try { try {
Notification::send($owner, new AdminRegistrationNotification($event->owner, $event->user)); Notification::send($owner, new AdminRegistrationNotification($event->owner, $event->user));
} catch (\Exception $e) { // @phpstan-ignore-line } catch (\Exception $e) { // @phpstan-ignore-line
@ -369,7 +370,7 @@ class UserEventHandler
public function storeUserIPAddress(ActuallyLoggedIn $event): void public function storeUserIPAddress(ActuallyLoggedIn $event): void
{ {
app('log')->debug('Now in storeUserIPAddress'); app('log')->debug('Now in storeUserIPAddress');
$user = $event->user; $user = $event->user;
if ($user->hasRole('demo')) { if ($user->hasRole('demo')) {
app('log')->debug('Do not log demo user logins'); app('log')->debug('Do not log demo user logins');
@ -386,8 +387,8 @@ class UserEventHandler
return; return;
} }
$inArray = false; $inArray = false;
$ip = request()->ip(); $ip = request()->ip();
app('log')->debug(sprintf('User logging in from IP address %s', $ip)); app('log')->debug(sprintf('User logging in from IP address %s', $ip));
// update array if in array // update array if in array
@ -415,7 +416,7 @@ class UserEventHandler
$preference = array_values($preference); $preference = array_values($preference);
/** @var bool $send */ /** @var bool $send */
$send = app('preferences')->getForUser($user, 'notification_user_login', true)->data; $send = app('preferences')->getForUser($user, 'notification_user_login', true)->data;
app('preferences')->setForUser($user, 'login_ip_history', $preference); app('preferences')->setForUser($user, 'login_ip_history', $preference);
if (false === $inArray && true === $send) { if (false === $inArray && true === $send) {

View File

@ -36,14 +36,14 @@ class PiggyBankObserver
{ {
app('log')->debug('Observe "created" of a piggy bank. DO NOTHING.'); app('log')->debug('Observe "created" of a piggy bank. DO NOTHING.');
// $repetition = new PiggyBankRepetition(); // $repetition = new PiggyBankRepetition();
// $repetition->piggyBank()->associate($piggyBank); // $repetition->piggyBank()->associate($piggyBank);
// $repetition->start_date = $piggyBank->start_date; // $repetition->start_date = $piggyBank->start_date;
// $repetition->start_date_tz = $piggyBank->start_date->format('e'); // $repetition->start_date_tz = $piggyBank->start_date->format('e');
// $repetition->target_date = $piggyBank->target_date; // $repetition->target_date = $piggyBank->target_date;
// $repetition->target_date_tz = $piggyBank->target_date?->format('e'); // $repetition->target_date_tz = $piggyBank->target_date?->format('e');
// $repetition->current_amount = '0'; // $repetition->current_amount = '0';
// $repetition->save(); // $repetition->save();
} }
/** /**

View File

@ -23,15 +23,9 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin; namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser; use FireflyIII\Http\Middleware\IsDemoUser;
use FireflyIII\Support\Notifications\UrlValidator;
use FireflyIII\User;
use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\Factory;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Redirector;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\View\View; use Illuminate\View\View;

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* NotificationController.php * NotificationController.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -36,25 +37,25 @@ class NotificationController extends Controller
public function index() public function index()
{ {
Log::channel('audit')->info('User visits notifications index.'); Log::channel('audit')->info('User visits notifications index.');
$title = (string) trans('firefly.administration'); $title = (string) trans('firefly.administration');
$mainTitleIcon = 'fa-hand-spock-o'; $mainTitleIcon = 'fa-hand-spock-o';
$subTitle = (string) trans('firefly.title_owner_notifications'); $subTitle = (string) trans('firefly.title_owner_notifications');
$subTitleIcon = 'envelope-o'; $subTitleIcon = 'envelope-o';
// notification settings: // notification settings:
$slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data; $slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data; $pushoverAppToken = app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data; $pushoverUserToken = app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
$ntfyServer = app('fireflyconfig')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data; $ntfyServer = app('fireflyconfig')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data; $ntfyTopic = app('fireflyconfig')->getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = app('fireflyconfig')->get('ntfy_auth', false)->data; $ntfyAuth = app('fireflyconfig')->get('ntfy_auth', false)->data;
$ntfyUser = app('fireflyconfig')->getEncrypted('ntfy_user', '')->data; $ntfyUser = app('fireflyconfig')->getEncrypted('ntfy_user', '')->data;
$ntfyPass = app('fireflyconfig')->getEncrypted('ntfy_pass', '')->data; $ntfyPass = app('fireflyconfig')->getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels'); $channels = config('notifications.channels');
$forcedAvailability = []; $forcedAvailability = [];
// admin notification settings: // admin notification settings:
$notifications = []; $notifications = [];
foreach (config('notifications.notifications.owner') as $key => $info) { foreach (config('notifications.notifications.owner') as $key => $info) {
if ($info['enabled']) { if ($info['enabled']) {
$notifications[$key] = app('fireflyconfig')->get(sprintf('notification_%s', $key), true)->data; $notifications[$key] = app('fireflyconfig')->get(sprintf('notification_%s', $key), true)->data;
@ -68,18 +69,31 @@ class NotificationController extends Controller
$forcedAvailability['ntfy'] = '' !== $ntfyTopic; $forcedAvailability['ntfy'] = '' !== $ntfyTopic;
$forcedAvailability['pushover'] = '' !== $pushoverAppToken && '' !== $pushoverUserToken; $forcedAvailability['pushover'] = '' !== $pushoverAppToken && '' !== $pushoverUserToken;
return view('admin.notifications.index', return view(
compact( 'admin.notifications.index',
'title', 'subTitle', 'forcedAvailability', 'mainTitleIcon', 'subTitleIcon', 'channels', compact(
'slackUrl', 'notifications', 'title',
'pushoverAppToken', 'pushoverUserToken', 'subTitle',
'ntfyServer', 'ntfyTopic', 'ntfyAuth', 'ntfyUser', 'ntfyPass' 'forcedAvailability',
)); 'mainTitleIcon',
'subTitleIcon',
'channels',
'slackUrl',
'notifications',
'pushoverAppToken',
'pushoverUserToken',
'ntfyServer',
'ntfyTopic',
'ntfyAuth',
'ntfyUser',
'ntfyPass'
)
);
} }
public function postIndex(NotificationRequest $request): RedirectResponse public function postIndex(NotificationRequest $request): RedirectResponse
{ {
$all = $request->getAll(); $all = $request->getAll();
foreach (config('notifications.notifications.owner') as $key => $info) { foreach (config('notifications.notifications.owner') as $key => $info) {
if (array_key_exists($key, $all)) { if (array_key_exists($key, $all)) {
@ -112,7 +126,9 @@ class NotificationController extends Controller
switch ($channel) { switch ($channel) {
default: default:
session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel])); session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));
break; break;
case 'email': case 'email':
case 'slack': case 'slack':
case 'pushover': case 'pushover':

View File

@ -28,7 +28,6 @@ use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Security\UnknownUserAttemptedLogin; use FireflyIII\Events\Security\UnknownUserAttemptedLogin;
use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Providers\RouteServiceProvider; use FireflyIII\Providers\RouteServiceProvider;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Contracts\Foundation\Application; use Illuminate\Contracts\Foundation\Application;
@ -70,7 +69,7 @@ class LoginController extends Controller
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
$this->username = 'email'; $this->username = 'email';
$this->middleware('guest')->except('logout'); $this->middleware('guest')->except('logout');
$this->repository = app(UserRepositoryInterface::class); $this->repository = app(UserRepositoryInterface::class);
} }
@ -128,7 +127,7 @@ class LoginController extends Controller
} }
app('log')->warning('Login attempt failed.'); app('log')->warning('Login attempt failed.');
$username = (string) $request->get($this->username()); $username = (string) $request->get($this->username());
if(null === $this->repository->findByEmail($username)) { if (null === $this->repository->findByEmail($username)) {
// send event to owner. // send event to owner.
event(new UnknownUserAttemptedLogin($username)); event(new UnknownUserAttemptedLogin($username));
} }

View File

@ -95,7 +95,7 @@ class RegisterController extends Controller
$this->validator($request->all())->validate(); $this->validator($request->all())->validate();
$user = $this->createUser($request->all()); $user = $this->createUser($request->all());
app('log')->info(sprintf('Registered new user %s', $user->email)); app('log')->info(sprintf('Registered new user %s', $user->email));
$owner = new OwnerNotifiable(); $owner = new OwnerNotifiable();
event(new RegisteredUser($owner, $user)); event(new RegisteredUser($owner, $user));
$this->guard()->login($user); $this->guard()->login($user);

View File

@ -84,7 +84,7 @@ class BudgetLimitController extends Controller
$budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end); $budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end);
// remove already budgeted currencies with the same date range // remove already budgeted currencies with the same date range
$currencies = $collection->filter( $currencies = $collection->filter(
static function (TransactionCurrency $currency) use ($budgetLimits, $start, $end) { static function (TransactionCurrency $currency) use ($budgetLimits, $start, $end) {
/** @var BudgetLimit $limit */ /** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) { foreach ($budgetLimits as $limit) {
@ -107,6 +107,7 @@ class BudgetLimitController extends Controller
public function show(BudgetLimit $budgetLimit) public function show(BudgetLimit $budgetLimit)
{ {
$notes = $this->blRepository->getNoteText($budgetLimit); $notes = $this->blRepository->getNoteText($budgetLimit);
return view('budgets.budget-limits.show', compact('budgetLimit', 'notes')); return view('budgets.budget-limits.show', compact('budgetLimit', 'notes'));
} }
@ -116,6 +117,7 @@ class BudgetLimitController extends Controller
public function edit(BudgetLimit $budgetLimit) public function edit(BudgetLimit $budgetLimit)
{ {
$notes = $this->blRepository->getNoteText($budgetLimit); $notes = $this->blRepository->getNoteText($budgetLimit);
return view('budgets.budget-limits.edit', compact('budgetLimit', 'notes')); return view('budgets.budget-limits.edit', compact('budgetLimit', 'notes'));
} }
@ -135,7 +137,7 @@ class BudgetLimitController extends Controller
* *
* @throws FireflyException * @throws FireflyException
*/ */
public function store(Request $request): JsonResponse | RedirectResponse public function store(Request $request): JsonResponse|RedirectResponse
{ {
app('log')->debug('Going to store new budget-limit.', $request->all()); app('log')->debug('Going to store new budget-limit.', $request->all());
// first search for existing one and update it if necessary. // first search for existing one and update it if necessary.
@ -144,14 +146,14 @@ class BudgetLimitController extends Controller
if (null === $currency || null === $budget) { if (null === $currency || null === $budget) {
throw new FireflyException('No valid currency or budget.'); throw new FireflyException('No valid currency or budget.');
} }
$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 (null === $start || null === $end) { if (null === $start || null === $end) {
return response()->json([]); return response()->json([]);
} }
$amount = (string) $request->get('amount'); $amount = (string) $request->get('amount');
$start->startOfDay(); $start->startOfDay();
$end->startOfDay(); $end->startOfDay();
@ -161,7 +163,7 @@ class BudgetLimitController extends Controller
app('log')->debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d'), $end->format('Y-m-d'))); app('log')->debug(sprintf('Start: %s, end: %s', $start->format('Y-m-d'), $end->format('Y-m-d')));
$limit = $this->blRepository->find($budget, $currency, $start, $end); $limit = $this->blRepository->find($budget, $currency, $start, $end);
// sanity check on amount: // sanity check on amount:
if (0 === bccomp($amount, '0')) { if (0 === bccomp($amount, '0')) {
@ -195,25 +197,25 @@ class BudgetLimitController extends Controller
); );
} }
// parse notes, if any. // parse notes, if any.
$notes = (string) $request->get('notes'); $notes = (string) $request->get('notes');
$this->blRepository->setNoteText($limit, $notes); $this->blRepository->setNoteText($limit, $notes);
if ($request->expectsJson()) { if ($request->expectsJson()) {
$array = $limit->toArray(); $array = $limit->toArray();
// add some extra metadata: // add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency); $spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection([$budget]), $currency);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0'; $array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount'])); $array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount']));
$array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']); $array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']);
$array['days_left'] = (string) $this->activeDaysLeft($start, $end); $array['days_left'] = (string) $this->activeDaysLeft($start, $end);
// left per day: // left per day:
$array['left_per_day'] = 0 === bccomp('0', $array['days_left']) ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']); $array['left_per_day'] = 0 === bccomp('0', $array['days_left']) ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
// left per day formatted. // left per day formatted.
$array['left_per_day_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $array['left_per_day']); $array['left_per_day_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $array['left_per_day']);
// notes: // notes:
$array['notes'] = $this->blRepository->getNoteText($limit); $array['notes'] = $this->blRepository->getNoteText($limit);
return response()->json($array); return response()->json($array);
} }
@ -223,7 +225,7 @@ class BudgetLimitController extends Controller
public function update(Request $request, BudgetLimit $budgetLimit): JsonResponse|RedirectResponse public function update(Request $request, BudgetLimit $budgetLimit): JsonResponse|RedirectResponse
{ {
$amount = (string) $request->get('amount'); $amount = (string) $request->get('amount');
if ('' === $amount) { if ('' === $amount) {
$amount = '0'; $amount = '0';
} }
@ -235,7 +237,7 @@ class BudgetLimitController extends Controller
$budgetId = $budgetLimit->budget_id; $budgetId = $budgetLimit->budget_id;
$currency = $budgetLimit->transactionCurrency; $currency = $budgetLimit->transactionCurrency;
$this->blRepository->destroyBudgetLimit($budgetLimit); $this->blRepository->destroyBudgetLimit($budgetLimit);
$array = [ $array = [
'budget_id' => $budgetId, 'budget_id' => $budgetId,
'left_formatted' => app('amount')->formatAnything($currency, '0'), 'left_formatted' => app('amount')->formatAnything($currency, '0'),
'left_per_day_formatted' => app('amount')->formatAnything($currency, '0'), 'left_per_day_formatted' => app('amount')->formatAnything($currency, '0'),
@ -248,29 +250,29 @@ class BudgetLimitController extends Controller
if (-1 === bccomp($amount, '0')) { if (-1 === bccomp($amount, '0')) {
$amount = bcmul($amount, '-1'); $amount = bcmul($amount, '-1');
} }
$notes = (string)$request->get('notes'); $notes = (string)$request->get('notes');
if(strlen($notes) > 32768) { if (strlen($notes) > 32768) {
$notes = substr($notes, 0, 32768); $notes = substr($notes, 0, 32768);
} }
$limit = $this->blRepository->update($budgetLimit, ['amount' => $amount,'notes' => $notes]); $limit = $this->blRepository->update($budgetLimit, ['amount' => $amount, 'notes' => $notes]);
app('preferences')->mark(); app('preferences')->mark();
$array = $limit->toArray(); $array = $limit->toArray();
$spentArr = $this->opsRepository->sumExpenses( $spentArr = $this->opsRepository->sumExpenses(
$limit->start_date, $limit->start_date,
$limit->end_date, $limit->end_date,
null, null,
new Collection([$budgetLimit->budget]), new Collection([$budgetLimit->budget]),
$budgetLimit->transactionCurrency $budgetLimit->transactionCurrency
); );
$daysLeft = $this->activeDaysLeft($limit->start_date, $limit->end_date); $daysLeft = $this->activeDaysLeft($limit->start_date, $limit->end_date);
$array['spent'] = $spentArr[$budgetLimit->transactionCurrency->id]['sum'] ?? '0'; $array['spent'] = $spentArr[$budgetLimit->transactionCurrency->id]['sum'] ?? '0';
$array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount'])); $array['left_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, bcadd($array['spent'], $array['amount']));
$array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']); $array['amount_formatted'] = app('amount')->formatAnything($limit->transactionCurrency, $limit['amount']);
$array['days_left'] = (string) $daysLeft; $array['days_left'] = (string) $daysLeft;
$array['left_per_day'] = 0 === $daysLeft ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']); $array['left_per_day'] = 0 === $daysLeft ? bcadd($array['spent'], $array['amount']) : bcdiv(bcadd($array['spent'], $array['amount']), $array['days_left']);
// left per day formatted. // left per day formatted.
$array['amount'] = app('steam')->bcround($limit['amount'], $limit->transactionCurrency->decimal_places); $array['amount'] = app('steam')->bcround($limit['amount'], $limit->transactionCurrency->decimal_places);
@ -278,6 +280,7 @@ class BudgetLimitController extends Controller
if ('true' === $request->get('redirect')) { if ('true' === $request->get('redirect')) {
return redirect(route('budgets.index')); return redirect(route('budgets.index'));
} }
return response()->json($array); return response()->json($array);
} }
} }

View File

@ -213,7 +213,7 @@ class IndexController extends Controller
$array['budgeted'][] = [ $array['budgeted'][] = [
'id' => $limit->id, 'id' => $limit->id,
'amount' => $amount, 'amount' => $amount,
'notes' => $this->blRepository->getNoteText($limit), 'notes' => $this->blRepository->getNoteText($limit),
'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat), 'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat), 'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end), 'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),

View File

@ -43,7 +43,6 @@ use Illuminate\Support\Facades\Log;
*/ */
class HomeController extends Controller class HomeController extends Controller
{ {
/** /**
* HomeController constructor. * HomeController constructor.
*/ */
@ -62,8 +61,8 @@ class HomeController extends Controller
*/ */
public function dateRange(Request $request): JsonResponse public function dateRange(Request $request): JsonResponse
{ {
$stringStart = ''; $stringStart = '';
$stringEnd = ''; $stringEnd = '';
try { try {
$stringStart = e((string) $request->get('start')); $stringStart = e((string) $request->get('start'));
@ -98,7 +97,7 @@ class HomeController extends Controller
app('log')->debug('Range is now marked as "custom".'); app('log')->debug('Range is now marked as "custom".');
} }
$diff = $start->diffInDays($end, true) + 1; $diff = $start->diffInDays($end, true) + 1;
if ($diff > 366) { if ($diff > 366) {
$request->session()->flash('warning', (string) trans('firefly.warning_much_data', ['days' => (int) $diff])); $request->session()->flash('warning', (string) trans('firefly.warning_much_data', ['days' => (int) $diff]));
@ -153,13 +152,13 @@ class HomeController extends Controller
} }
/** @var Carbon $start */ /** @var Carbon $start */
$start = session('start', today(config('app.timezone'))->startOfMonth()); $start = session('start', today(config('app.timezone'))->startOfMonth());
/** @var Carbon $end */ /** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
$accounts = $repository->getAccountsById($frontpageArray); $accounts = $repository->getAccountsById($frontpageArray);
$today = today(config('app.timezone')); $today = today(config('app.timezone'));
$accounts = $accounts->sortBy('order'); // sort frontpage accounts by order $accounts = $accounts->sortBy('order'); // sort frontpage accounts by order
app('log')->debug('Frontpage accounts are ', $frontpageArray); app('log')->debug('Frontpage accounts are ', $frontpageArray);
@ -169,14 +168,14 @@ class HomeController extends Controller
// collect groups for each transaction. // collect groups for each transaction.
foreach ($accounts as $account) { foreach ($accounts as $account) {
/** @var GroupCollectorInterface $collector */ /** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class); $collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->withAccountInformation()->setRange($start, $end)->setLimit(10)->setPage(1); $collector->setAccounts(new Collection([$account]))->withAccountInformation()->setRange($start, $end)->setLimit(10)->setPage(1);
$set = $collector->getExtractedJournals(); $set = $collector->getExtractedJournals();
$transactions[] = ['transactions' => $set, 'account' => $account]; $transactions[] = ['transactions' => $set, 'account' => $account];
} }
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
event(new RequestedVersionCheckStatus($user)); event(new RequestedVersionCheckStatus($user));
return view('index', compact('count', 'subTitle', 'transactions', 'billCount', 'start', 'end', 'today', 'pageTitle')); return view('index', compact('count', 'subTitle', 'transactions', 'billCount', 'start', 'end', 'today', 'pageTitle'));
@ -187,11 +186,11 @@ class HomeController extends Controller
$subTitle = (string) trans('firefly.welcome_back'); $subTitle = (string) trans('firefly.welcome_back');
$pageTitle = (string) trans('firefly.main_dashboard_page_title'); $pageTitle = (string) trans('firefly.main_dashboard_page_title');
$start = session('start', today(config('app.timezone'))->startOfMonth()); $start = session('start', today(config('app.timezone'))->startOfMonth());
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
event(new RequestedVersionCheckStatus($user)); event(new RequestedVersionCheckStatus($user));
return view('index', compact('subTitle', 'start', 'end', 'pageTitle')); return view('index', compact('subTitle', 'start', 'end', 'pageTitle'));

View File

@ -94,7 +94,7 @@ class CreateController extends Controller
*/ */
public function store(PiggyBankStoreRequest $request) public function store(PiggyBankStoreRequest $request)
{ {
$data = $request->getPiggyBankData(); $data = $request->getPiggyBankData();
if (null === $data['start_date']) { if (null === $data['start_date']) {
$data['start_date'] = today(config('app.timezone')); $data['start_date'] = today(config('app.timezone'));
@ -107,7 +107,7 @@ class CreateController extends Controller
// store attachment(s): // store attachment(s):
/** @var null|array $files */ /** @var null|array $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($piggyBank, $files); $this->attachments->saveAttachmentsForModel($piggyBank, $files);
} }
@ -119,7 +119,7 @@ class CreateController extends Controller
if (count($this->attachments->getMessages()->get('attachments')) > 0) { if (count($this->attachments->getMessages()->get('attachments')) > 0) {
$request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); $request->session()->flash('info', $this->attachments->getMessages()->get('attachments'));
} }
$redirect = redirect($this->getPreviousUrl('piggy-banks.create.url')); $redirect = redirect($this->getPreviousUrl('piggy-banks.create.url'));
if (1 === (int) $request->get('create_another')) { if (1 === (int) $request->get('create_another')) {
session()->put('piggy-banks.create.fromStore', true); session()->put('piggy-banks.create.fromStore', true);

View File

@ -81,13 +81,13 @@ class IndexController extends Controller
{ {
$this->cleanupObjectGroups(); $this->cleanupObjectGroups();
$this->piggyRepos->resetOrder(); $this->piggyRepos->resetOrder();
$collection = $this->piggyRepos->getPiggyBanks(); $collection = $this->piggyRepos->getPiggyBanks();
/** @var Carbon $end */ /** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
// transform piggies using the transformer: // transform piggies using the transformer:
$parameters = new ParameterBag(); $parameters = new ParameterBag();
$parameters->set('end', $end); $parameters->set('end', $end);
@ -96,10 +96,10 @@ class IndexController extends Controller
$accountTransformer->setParameters($parameters); $accountTransformer->setParameters($parameters);
// data // data
$piggyBanks = $this->groupPiggyBanks($collection); $piggyBanks = $this->groupPiggyBanks($collection);
$accounts = $this->collectAccounts($collection); $accounts = $this->collectAccounts($collection);
$accounts = $this->mergeAccountsAndPiggies($piggyBanks, $accounts); $accounts = $this->mergeAccountsAndPiggies($piggyBanks, $accounts);
$piggyBanks = $this->makeSums($piggyBanks); $piggyBanks = $this->makeSums($piggyBanks);
ksort($piggyBanks); ksort($piggyBanks);
@ -112,7 +112,7 @@ class IndexController extends Controller
foreach ($piggyBanks as $groupOrder => $group) { foreach ($piggyBanks as $groupOrder => $group) {
$groupId = $group['object_group_id']; $groupId = $group['object_group_id'];
foreach ($group['piggy_banks'] as $piggy) { foreach ($group['piggy_banks'] as $piggy) {
$currencyId = $piggy['currency_id']; $currencyId = $piggy['currency_id'];
$sums[$groupId][$currencyId] ??= [ $sums[$groupId][$currencyId] ??= [
'target' => '0', 'target' => '0',
'saved' => '0', 'saved' => '0',
@ -164,47 +164,51 @@ class IndexController extends Controller
/** @var PiggyBankTransformer $transformer */ /** @var PiggyBankTransformer $transformer */
$transformer = app(PiggyBankTransformer::class); $transformer = app(PiggyBankTransformer::class);
$transformer->setParameters(new ParameterBag()); $transformer->setParameters(new ParameterBag());
$piggyBanks = []; $piggyBanks = [];
/** @var PiggyBank $piggy */ /** @var PiggyBank $piggy */
foreach ($collection as $piggy) { foreach ($collection as $piggy) {
$array = $transformer->transform($piggy); $array = $transformer->transform($piggy);
$groupOrder = (int) $array['object_group_order']; $groupOrder = (int) $array['object_group_order'];
$piggyBanks[$groupOrder] ??= [ $piggyBanks[$groupOrder] ??= [
'object_group_id' => $array['object_group_id'] ?? 0, 'object_group_id' => $array['object_group_id'] ?? 0,
'object_group_title' => $array['object_group_title'] ?? trans('firefly.default_group_title_name'), 'object_group_title' => $array['object_group_title'] ?? trans('firefly.default_group_title_name'),
'piggy_banks' => [], 'piggy_banks' => [],
]; ];
$array['attachments'] = $this->piggyRepos->getAttachments($piggy); $array['attachments'] = $this->piggyRepos->getAttachments($piggy);
// sum the total amount for the index. // sum the total amount for the index.
$piggyBanks[$groupOrder]['piggy_banks'][] = $array; $piggyBanks[$groupOrder]['piggy_banks'][] = $array;
} }
return $piggyBanks; return $piggyBanks;
} }
private function collectAccounts(Collection $collection): array private function collectAccounts(Collection $collection): array
{ {
/** @var Carbon $end */ /** @var Carbon $end */
$end = session('end', today(config('app.timezone'))->endOfMonth()); $end = session('end', today(config('app.timezone'))->endOfMonth());
// transform piggies using the transformer: // transform piggies using the transformer:
$parameters = new ParameterBag(); $parameters = new ParameterBag();
$parameters->set('end', $end); $parameters->set('end', $end);
/** @var AccountTransformer $accountTransformer */ /** @var AccountTransformer $accountTransformer */
$accountTransformer = app(AccountTransformer::class); $accountTransformer = app(AccountTransformer::class);
$accountTransformer->setParameters($parameters); $accountTransformer->setParameters($parameters);
$return = []; $return = [];
/** @var PiggyBank $piggy */ /** @var PiggyBank $piggy */
foreach ($collection as $piggy) { foreach ($collection as $piggy) {
$accounts = $piggy->accounts; $accounts = $piggy->accounts;
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
$array = $accountTransformer->transform($account); $array = $accountTransformer->transform($account);
$accountId = (int) $array['id']; $accountId = (int) $array['id'];
if (!array_key_exists($accountId, $return)) { if (!array_key_exists($accountId, $return)) {
$return[$accountId] = $array; $return[$accountId] = $array;
// add some interesting details: // add some interesting details:
$return[$accountId]['left'] = $return[$accountId]['current_balance']; $return[$accountId]['left'] = $return[$accountId]['current_balance'];
@ -214,20 +218,21 @@ class IndexController extends Controller
} }
// calculate new interesting fields: // calculate new interesting fields:
// $return[$accountId]['left'] -= $array['current_amount']; // $return[$accountId]['left'] -= $array['current_amount'];
// $return[$accountId]['saved'] += $array['current_amount']; // $return[$accountId]['saved'] += $array['current_amount'];
// $return[$accountId]['target'] += $array['target_amount']; // $return[$accountId]['target'] += $array['target_amount'];
// $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']); // $return[$accountId]['to_save'] += ($array['target_amount'] - $array['current_amount']);
// $return['account_name'] = $account['name']; // $return['account_name'] = $account['name'];
} }
} }
return $return; return $return;
} }
private function mergeAccountsAndPiggies(array $piggyBanks, array $accounts): array private function mergeAccountsAndPiggies(array $piggyBanks, array $accounts): array
{ {
/** @var array $piggyBank */ // @var array $piggyBank
foreach ($piggyBanks as $group) { foreach ($piggyBanks as $group) {
foreach ($group['piggy_banks'] as $piggyBank) { foreach ($group['piggy_banks'] as $piggyBank) {
// loop all accounts in this piggy bank subtract the current amount from "left to save" in the $accounts array. // loop all accounts in this piggy bank subtract the current amount from "left to save" in the $accounts array.
@ -243,6 +248,7 @@ class IndexController extends Controller
} }
} }
} }
return $accounts; return $accounts;
} }
} }

View File

@ -66,14 +66,14 @@ class PreferencesController extends Controller
*/ */
public function index(AccountRepositoryInterface $repository) public function index(AccountRepositoryInterface $repository)
{ {
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]);
$isDocker = env('IS_DOCKER', false); $isDocker = env('IS_DOCKER', false);
$groupedAccounts = []; $groupedAccounts = [];
/** @var Account $account */ /** @var Account $account */
foreach ($accounts as $account) { foreach ($accounts as $account) {
$type = $account->accountType->type; $type = $account->accountType->type;
$role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role')); $role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));
if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) { if (in_array($type, [AccountType::MORTGAGE, AccountType::DEBT, AccountType::LOAN], true)) {
$role = sprintf('opt_group_l_%s', $type); $role = sprintf('opt_group_l_%s', $type);
@ -94,42 +94,49 @@ class PreferencesController extends Controller
if (!is_array($frontpageAccounts)) { if (!is_array($frontpageAccounts)) {
$frontpageAccounts = $accountIds; $frontpageAccounts = $accountIds;
} }
$language = app('steam')->getLanguage(); $language = app('steam')->getLanguage();
$languages = config('firefly.languages'); $languages = config('firefly.languages');
$locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data; $locale = app('preferences')->get('locale', config('firefly.default_locale', 'equal'))->data;
$listPageSize = app('preferences')->get('listPageSize', 50)->data; $listPageSize = app('preferences')->get('listPageSize', 50)->data;
$darkMode = app('preferences')->get('darkMode', 'browser')->data; $darkMode = app('preferences')->get('darkMode', 'browser')->data;
$customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data; $customFiscalYear = app('preferences')->get('customFiscalYear', 0)->data;
$fiscalYearStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data; $fiscalYearStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data;
if (is_array($fiscalYearStartStr)) { if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01'; $fiscalYearStartStr = '01-01';
} }
$fiscalYearStart = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr); $fiscalYearStart = sprintf('%s-%s', date('Y'), (string) $fiscalYearStartStr);
$tjOptionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; $tjOptionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data;
$availableDarkModes = config('firefly.available_dark_modes'); $availableDarkModes = config('firefly.available_dark_modes');
// notifications settings // notifications settings
$slackUrl = app('preferences')->getEncrypted('slack_webhook_url', '')->data; $slackUrl = app('preferences')->getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = app('preferences')->getEncrypted('pushover_app_token', '')->data; $pushoverAppToken = app('preferences')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = app('preferences')->getEncrypted('pushover_user_token', '')->data; $pushoverUserToken = app('preferences')->getEncrypted('pushover_user_token', '')->data;
$ntfyServer = app('preferences')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data; $ntfyServer = app('preferences')->getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = app('preferences')->getEncrypted('ntfy_topic', '')->data; $ntfyTopic = app('preferences')->getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = app('preferences')->get('ntfy_auth', false)->data; $ntfyAuth = app('preferences')->get('ntfy_auth', false)->data;
$ntfyUser = app('preferences')->getEncrypted('ntfy_user', '')->data; $ntfyUser = app('preferences')->getEncrypted('ntfy_user', '')->data;
$ntfyPass = app('preferences')->getEncrypted('ntfy_pass', '')->data; $ntfyPass = app('preferences')->getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels');
$forcedAvailability = [];
// notification preferences
// notification preferences (single value for each): $notifications = [];
$notifications = [];
foreach (config('notifications.notifications.user') as $key => $info) { foreach (config('notifications.notifications.user') as $key => $info) {
if ($info['enabled']) { if ($info['enabled']) {
$notifications[$key] $notifications[$key]
= [ = [
'enabled' => app('preferences')->get(sprintf('notification_%s', $key), true)->data, 'enabled' => app('preferences')->get(sprintf('notification_%s', $key), true)->data,
'configurable' => $info['configurable'], 'configurable' => $info['configurable'],
]; ];
} }
} }
// loop all channels to see if they are available.
foreach ($channels as $channel => $info) {
$forcedAvailability[$channel] = true;
}
$forcedAvailability['ntfy'] = '' !== $ntfyTopic;
$forcedAvailability['pushover'] = '' !== $pushoverAppToken && '' !== $pushoverUserToken;
ksort($languages); ksort($languages);
@ -141,7 +148,7 @@ class PreferencesController extends Controller
app('log')->error($e->getMessage()); app('log')->error($e->getMessage());
$locales = []; $locales = [];
} }
$locales = ['equal' => (string) trans('firefly.equal_to_language')] + $locales; $locales = ['equal' => (string) trans('firefly.equal_to_language')] + $locales;
// an important fallback is that the frontPageAccount array gets refilled automatically // an important fallback is that the frontPageAccount array gets refilled automatically
// when it turns up empty. // when it turns up empty.
if (0 === count($frontpageAccounts)) { if (0 === count($frontpageAccounts)) {
@ -151,7 +158,7 @@ class PreferencesController extends Controller
// for the demo user, the notification settings are automatically emptied. // for the demo user, the notification settings are automatically emptied.
// this isn't really secure, but it means that the demo site has semi-secret notification settings. // this isn't really secure, but it means that the demo site has semi-secret notification settings.
if (auth()->user()->hasRole('demo')) { if (auth()->user()->hasRole('demo')) {
$slackUrl = ''; $slackUrl = '';
$pushoverAppToken = ''; $pushoverAppToken = '';
$pushoverUserToken = ''; $pushoverUserToken = '';
$ntfyServer = ''; $ntfyServer = '';
@ -161,9 +168,32 @@ class PreferencesController extends Controller
$ntfyPass = ''; $ntfyPass = '';
} }
return view('preferences.index', compact('language', 'pushoverAppToken', 'pushoverUserToken', return view('preferences.index', compact(
'ntfyServer', 'ntfyTopic', 'ntfyAuth', 'ntfyUser', 'ntfyPass', 'language',
'groupedAccounts', 'isDocker', 'frontpageAccounts', 'languages', 'darkMode', 'availableDarkModes', 'notifications', 'slackUrl', 'locales', 'locale', 'tjOptionalFields', 'viewRange', 'customFiscalYear', 'listPageSize', 'fiscalYearStart')); 'pushoverAppToken',
'pushoverUserToken',
'ntfyServer',
'ntfyTopic',
'ntfyAuth',
'ntfyUser',
'forcedAvailability',
'ntfyPass',
'groupedAccounts',
'isDocker',
'frontpageAccounts',
'languages',
'darkMode',
'availableDarkModes',
'notifications',
'slackUrl',
'locales',
'locale',
'tjOptionalFields',
'viewRange',
'customFiscalYear',
'listPageSize',
'fiscalYearStart'
));
} }
/** /**
@ -188,8 +218,9 @@ class PreferencesController extends Controller
} }
// extract notifications: // extract notifications:
$all = $request->all(); $all = $request->all();
die('fix the reference to the available notifications.');
exit('fix the reference to the available notifications.');
foreach (config('firefly.available_notifications') as $option) { foreach (config('firefly.available_notifications') as $option) {
$key = sprintf('notification_%s', $option); $key = sprintf('notification_%s', $option);
if (array_key_exists($key, $all)) { if (array_key_exists($key, $all)) {
@ -219,8 +250,8 @@ class PreferencesController extends Controller
} }
// custom fiscal year // custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear'); $customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$string = strtotime((string) $request->get('fiscalYearStart')); $string = strtotime((string) $request->get('fiscalYearStart'));
if (false !== $string) { if (false !== $string) {
$fiscalYearStart = date('m-d', $string); $fiscalYearStart = date('m-d', $string);
app('preferences')->set('customFiscalYear', $customFiscalYear); app('preferences')->set('customFiscalYear', $customFiscalYear);
@ -229,15 +260,15 @@ class PreferencesController extends Controller
// save page size: // save page size:
app('preferences')->set('listPageSize', 50); app('preferences')->set('listPageSize', 50);
$listPageSize = (int) $request->get('listPageSize'); $listPageSize = (int) $request->get('listPageSize');
if ($listPageSize > 0 && $listPageSize < 1337) { if ($listPageSize > 0 && $listPageSize < 1337) {
app('preferences')->set('listPageSize', $listPageSize); app('preferences')->set('listPageSize', $listPageSize);
} }
// language: // language:
/** @var Preference $currentLang */ /** @var Preference $currentLang */
$currentLang = app('preferences')->get('language', 'en_US'); $currentLang = app('preferences')->get('language', 'en_US');
$lang = $request->get('language'); $lang = $request->get('language');
if (array_key_exists($lang, config('firefly.languages'))) { if (array_key_exists($lang, config('firefly.languages'))) {
app('preferences')->set('language', $lang); app('preferences')->set('language', $lang);
} }
@ -254,8 +285,8 @@ class PreferencesController extends Controller
} }
// optional fields for transactions: // optional fields for transactions:
$setOptions = $request->get('tj') ?? []; $setOptions = $request->get('tj') ?? [];
$optionalTj = [ $optionalTj = [
'interest_date' => array_key_exists('interest_date', $setOptions), 'interest_date' => array_key_exists('interest_date', $setOptions),
'book_date' => array_key_exists('book_date', $setOptions), 'book_date' => array_key_exists('book_date', $setOptions),
'process_date' => array_key_exists('process_date', $setOptions), 'process_date' => array_key_exists('process_date', $setOptions),
@ -272,7 +303,7 @@ class PreferencesController extends Controller
app('preferences')->set('transaction_journal_optional_fields', $optionalTj); app('preferences')->set('transaction_journal_optional_fields', $optionalTj);
// dark mode // dark mode
$darkMode = $request->get('darkMode') ?? 'browser'; $darkMode = $request->get('darkMode') ?? 'browser';
if (in_array($darkMode, config('firefly.available_dark_modes'), true)) { if (in_array($darkMode, config('firefly.available_dark_modes'), true)) {
app('preferences')->set('darkMode', $darkMode); app('preferences')->set('darkMode', $darkMode);
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* NotificationRequest.php * NotificationRequest.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -35,24 +36,25 @@ class NotificationRequest extends FormRequest
public function getAll(): array public function getAll(): array
{ {
$return = []; $return = [];
foreach (config('notifications.notifications.owner') as $key => $info) { foreach (config('notifications.notifications.owner') as $key => $info) {
$value = false; $value = false;
if ($this->has(sprintf('notification_%s', $key))) { if ($this->has(sprintf('notification_%s', $key))) {
$value = true; $value = true;
} }
$return[$key] = $value; $return[$key] = $value;
} }
$return['slack_webhook_url'] = $this->convertString('slack_webhook_url'); $return['slack_webhook_url'] = $this->convertString('slack_webhook_url');
$return['pushover_app_token'] = $this->convertString('pushover_app_token'); $return['pushover_app_token'] = $this->convertString('pushover_app_token');
$return['pushover_user_token'] = $this->convertString('pushover_user_token'); $return['pushover_user_token'] = $this->convertString('pushover_user_token');
$return['ntfy_server'] = $this->convertString('ntfy_server'); $return['ntfy_server'] = $this->convertString('ntfy_server');
$return['ntfy_topic'] = $this->convertString('ntfy_topic'); $return['ntfy_topic'] = $this->convertString('ntfy_topic');
$return['ntfy_auth'] = $this->convertBoolean($this->get('ntfy_auth')); $return['ntfy_auth'] = $this->convertBoolean($this->get('ntfy_auth'));
$return['ntfy_user'] = $this->convertString('ntfy_user'); $return['ntfy_user'] = $this->convertString('ntfy_user');
$return['ntfy_pass'] = $this->convertString('ntfy_pass'); $return['ntfy_pass'] = $this->convertString('ntfy_pass');
return $return; return $return;
} }
@ -63,14 +65,14 @@ class NotificationRequest extends FormRequest
{ {
$rules = [ $rules = [
'slack_webhook_url' => ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()], 'slack_webhook_url' => ['nullable', 'url', 'min:1', new IsValidSlackOrDiscordUrl()],
'ntfy_server' => ['nullable', 'url', 'min:1'], 'ntfy_server' => ['nullable', 'url', 'min:1'],
'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'], 'ntfy_user' => ['required_with:ntfy_pass,ntfy_auth', 'nullable', 'string', 'min:1'],
'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'], 'ntfy_pass' => ['required_with:ntfy_user,ntfy_auth', 'nullable', 'string', 'min:1'],
]; ];
foreach (config('notifications.notifications.owner') as $key => $info) { foreach (config('notifications.notifications.owner') as $key => $info) {
$rules[sprintf('notification_%s', $key)] = 'in:0,1'; $rules[sprintf('notification_%s', $key)] = 'in:0,1';
} }
return $rules; return $rules;
} }
} }

View File

@ -59,7 +59,7 @@ class PiggyBankStoreRequest extends FormRequest
$accounts = []; $accounts = [];
} }
foreach ($accounts as $item) { foreach ($accounts as $item) {
$data['accounts'][] = ['account_id' => (int) ($item)]; $data['accounts'][] = ['account_id' => (int) $item];
} }
return $data; return $data;
@ -105,7 +105,7 @@ class PiggyBankStoreRequest extends FormRequest
if ($accountCurrency->id !== $currency->id && 'true' !== $isMultiCurrency) { if ($accountCurrency->id !== $currency->id && 'true' !== $isMultiCurrency) {
$validator->errors()->add('accounts', trans('validation.invalid_account_currency')); $validator->errors()->add('accounts', trans('validation.invalid_account_currency'));
} }
$type = $account->accountType->type; $type = $account->accountType->type;
if (!in_array($type, $types, true)) { if (!in_array($type, $types, true)) {
$validator->errors()->add('accounts', trans('validation.invalid_account_type')); $validator->errors()->add('accounts', trans('validation.invalid_account_type'));
} }
@ -127,6 +127,7 @@ class PiggyBankStoreRequest extends FormRequest
if (null === $currency) { if (null === $currency) {
return app('amount')->getDefaultCurrency(); return app('amount')->getDefaultCurrency();
} }
return $currency; return $currency;
} }
} }

View File

@ -44,18 +44,18 @@ class PiggyBank extends Model
protected $casts protected $casts
= [ = [
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'deleted_at' => 'datetime', 'deleted_at' => 'datetime',
'start_date' => 'date', 'start_date' => 'date',
'target_date' => 'date', 'target_date' => 'date',
'order' => 'int', 'order' => 'int',
'active' => 'boolean', 'active' => 'boolean',
'encrypted' => 'boolean', 'encrypted' => 'boolean',
'target_amount' => 'string', 'target_amount' => 'string',
]; ];
protected $fillable = ['name', 'account_id', 'order', 'target_amount', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'active','transaction_currency_id']; protected $fillable = ['name', 'account_id', 'order', 'target_amount', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'active', 'transaction_currency_id'];
/** /**
* Route binder. Converts the key in the URL to the specified object (or throw 404). * Route binder. Converts the key in the URL to the specified object (or throw 404).
@ -67,9 +67,10 @@ class PiggyBank extends Model
if (auth()->check()) { if (auth()->check()) {
$piggyBankId = (int)$value; $piggyBankId = (int)$value;
$piggyBank = self::where('piggy_banks.id', $piggyBankId) $piggyBank = self::where('piggy_banks.id', $piggyBankId)
->leftJoin('account_piggy_bank','account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id') ->leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', auth()->user()->id)->first(['piggy_banks.*']); ->where('accounts.user_id', auth()->user()->id)->first(['piggy_banks.*'])
;
if (null !== $piggyBank) { if (null !== $piggyBank) {
return $piggyBank; return $piggyBank;
} }

View File

@ -40,11 +40,11 @@ class PiggyBankRepetition extends Model
protected $casts protected $casts
= [ = [
'created_at' => 'datetime', 'created_at' => 'datetime',
'updated_at' => 'datetime', 'updated_at' => 'datetime',
'start_date' => SeparateTimezoneCaster::class, 'start_date' => SeparateTimezoneCaster::class,
'target_date' => SeparateTimezoneCaster::class, 'target_date' => SeparateTimezoneCaster::class,
'virtual_balance' => 'string', 'virtual_balance' => 'string',
]; ];
protected $fillable = ['piggy_bank_id', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'current_amount']; protected $fillable = ['piggy_bank_id', 'start_date', 'start_date_tz', 'target_date', 'target_date_tz', 'current_amount'];

View File

@ -38,7 +38,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\MorphMany; use Illuminate\Database\Eloquent\Relations\MorphMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/** /**

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* UnknownUserLoginAttempt.php * UnknownUserLoginAttempt.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -64,15 +65,14 @@ class UnknownUserLoginAttempt extends Notification
/** /**
* Get the mail representation of the notification. * Get the mail representation of the notification.
* *
* @return MailMessage
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/ */
public function toMail(OwnerNotifiable $notifiable): MailMessage public function toMail(OwnerNotifiable $notifiable): MailMessage
{ {
return new MailMessage() return new MailMessage()
->markdown('emails.owner.unknown-user', ['address' => $this->address]) ->markdown('emails.owner.unknown-user', ['address' => $this->address])
->subject((string) trans('email.unknown_user_subject')); ->subject((string) trans('email.unknown_user_subject'))
;
} }
/** /**
@ -90,7 +90,8 @@ class UnknownUserLoginAttempt extends Notification
public function toPushover(OwnerNotifiable $notifiable): PushoverMessage public function toPushover(OwnerNotifiable $notifiable): PushoverMessage
{ {
return PushoverMessage::create((string) trans('email.unknown_user_message', ['address' => $this->address])) return PushoverMessage::create((string) trans('email.unknown_user_message', ['address' => $this->address]))
->title((string) trans('email.unknown_user_subject')); ->title((string) trans('email.unknown_user_subject'))
;
} }
public function toNtfy(OwnerNotifiable $notifiable): Message public function toNtfy(OwnerNotifiable $notifiable): Message
@ -108,7 +109,7 @@ class UnknownUserLoginAttempt extends Notification
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
} }
$message = new Message(); $message = new Message();
$message->topic($settings['ntfy_topic']); $message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.unknown_user_subject')); $message->title((string) trans('email.unknown_user_subject'));
$message->body((string) trans('email.unknown_user_message', ['address' => $this->address])); $message->body((string) trans('email.unknown_user_message', ['address' => $this->address]));

View File

@ -83,7 +83,8 @@ class UserInvitation extends Notification
{ {
return (new MailMessage()) return (new MailMessage())
->markdown('emails.invitation-created', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]) ->markdown('emails.invitation-created', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email])
->subject((string) trans('email.invitation_created_subject')); ->subject((string) trans('email.invitation_created_subject'))
;
} }
/** /**
@ -107,7 +108,8 @@ class UserInvitation extends Notification
Log::debug('Now in toPushover() for UserInvitation'); Log::debug('Now in toPushover() for UserInvitation');
return PushoverMessage::create((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email])) return PushoverMessage::create((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]))
->title((string) trans('email.invitation_created_subject')); ->title((string) trans('email.invitation_created_subject'))
;
} }
public function toNtfy(OwnerNotifiable $notifiable): Message public function toNtfy(OwnerNotifiable $notifiable): Message
@ -126,7 +128,7 @@ class UserInvitation extends Notification
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
} }
$message = new Message(); $message = new Message();
$message->topic($settings['ntfy_topic']); $message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.invitation_created_subject')); $message->title((string) trans('email.invitation_created_subject'));
$message->body((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email])); $message->body((string) trans('email.invitation_created_body', ['email' => $this->invitee->user->email, 'invitee' => $this->invitee->email]));

View File

@ -51,7 +51,7 @@ class UserRegistration extends Notification
*/ */
public function __construct(OwnerNotifiable $owner, User $user) public function __construct(OwnerNotifiable $owner, User $user)
{ {
$this->user = $user; $this->user = $user;
$this->owner = $owner; $this->owner = $owner;
} }
@ -79,7 +79,8 @@ class UserRegistration extends Notification
{ {
return (new MailMessage()) return (new MailMessage())
->markdown('emails.registered-admin', ['email' => $this->user->email, 'id' => $this->user->id]) ->markdown('emails.registered-admin', ['email' => $this->user->email, 'id' => $this->user->id])
->subject((string) trans('email.registered_subject_admin')); ->subject((string) trans('email.registered_subject_admin'))
;
} }
/** /**
@ -101,7 +102,8 @@ class UserRegistration extends Notification
Log::debug('Now in toPushover() for UserRegistration'); Log::debug('Now in toPushover() for UserRegistration');
return PushoverMessage::create((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'invitee' => $this->user->email])) return PushoverMessage::create((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'invitee' => $this->user->email]))
->title((string) trans('email.registered_subject_admin')); ->title((string) trans('email.registered_subject_admin'))
;
} }
public function toNtfy(OwnerNotifiable $notifiable): Message public function toNtfy(OwnerNotifiable $notifiable): Message
@ -120,10 +122,10 @@ class UserRegistration extends Notification
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
} }
$message = new Message(); $message = new Message();
$message->topic($settings['ntfy_topic']); $message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.registered_subject_admin')); $message->title((string) trans('email.registered_subject_admin'));
$message->body((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'invitee' => $this->user->email]) ); $message->body((string) trans('email.admin_new_user_registered', ['email' => $this->user->email, 'invitee' => $this->user->email]));
return $message; return $message;
} }

View File

@ -27,7 +27,6 @@ namespace FireflyIII\Notifications\Admin;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable; use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Notifications\ReturnsAvailableChannels; use FireflyIII\Notifications\ReturnsAvailableChannels;
use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\Notifications\ReturnsSettings;
use FireflyIII\Support\Notifications\UrlValidator;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Messages\SlackMessage;
@ -108,7 +107,8 @@ class VersionCheckResult extends Notification
Log::debug('Now in toPushover() for VersionCheckResult'); Log::debug('Now in toPushover() for VersionCheckResult');
return PushoverMessage::create($this->message) return PushoverMessage::create($this->message)
->title((string) trans('email.new_version_email_subject')); ->title((string) trans('email.new_version_email_subject'))
;
} }
public function toNtfy(OwnerNotifiable $notifiable): Message public function toNtfy(OwnerNotifiable $notifiable): Message
@ -127,7 +127,7 @@ class VersionCheckResult extends Notification
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
} }
$message = new Message(); $message = new Message();
$message->topic($settings['ntfy_topic']); $message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.new_version_email_subject')); $message->title((string) trans('email.new_version_email_subject'));
$message->body($this->message); $message->body($this->message);

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* AdminNotifiable.php * AdminNotifiable.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -30,13 +31,13 @@ use NotificationChannels\Pushover\PushoverReceiver;
class OwnerNotifiable class OwnerNotifiable
{ {
public function routeNotificationForSlack(): string public function routeNotificationForSlack(): string
{ {
$res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data; $res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (is_array($res)) { if (is_array($res)) {
$res = ''; $res = '';
} }
return (string) $res; return (string) $res;
} }
@ -45,8 +46,10 @@ class OwnerNotifiable
Log::debug('Return settings for routeNotificationForPushover'); Log::debug('Return settings for routeNotificationForPushover');
$pushoverAppToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data; $pushoverAppToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data; $pushoverUserToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
return PushoverReceiver::withUserKey($pushoverUserToken) return PushoverReceiver::withUserKey($pushoverUserToken)
->withApplicationToken($pushoverAppToken); ->withApplicationToken($pushoverAppToken)
;
} }
/** /**
@ -59,9 +62,10 @@ class OwnerNotifiable
*/ */
public function routeNotificationFor($driver, $notification = null) public function routeNotificationFor($driver, $notification = null)
{ {
$method = 'routeNotificationFor' . Str::studly($driver); $method = 'routeNotificationFor'.Str::studly($driver);
if (method_exists($this, $method)) { if (method_exists($this, $method)) {
Log::debug(sprintf('Redirect for settings to "%s".', $method)); Log::debug(sprintf('Redirect for settings to "%s".', $method));
return $this->{$method}($notification); // @phpstan-ignore-line return $this->{$method}($notification); // @phpstan-ignore-line
} }
Log::debug(sprintf('No method "%s" found, return generic settings.', $method)); Log::debug(sprintf('No method "%s" found, return generic settings.', $method));

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* ReturnsAvailableChannels.php * ReturnsAvailableChannels.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -46,8 +47,8 @@ class ReturnsAvailableChannels
private static function returnOwnerChannels(): array private static function returnOwnerChannels(): array
{ {
$channels = ['mail']; $channels = ['mail'];
$slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data; $slackUrl = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (UrlValidator::isValidWebhookURL($slackUrl)) { if (UrlValidator::isValidWebhookURL($slackUrl)) {
$channels[] = 'slack'; $channels[] = 'slack';
} }
@ -73,8 +74,8 @@ class ReturnsAvailableChannels
} }
Log::debug(sprintf('Final channel set in ReturnsAvailableChannels: %s ', implode(', ', $channels))); Log::debug(sprintf('Final channel set in ReturnsAvailableChannels: %s ', implode(', ', $channels)));
// only the owner can get notifications over // only the owner can get notifications over
return $channels; return $channels;
} }
} }

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* ReturnsSettings.php * ReturnsSettings.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -34,6 +35,7 @@ class ReturnsSettings
if ('ntfy' === $channel) { if ('ntfy' === $channel) {
return self::getNtfySettings($type, $user); return self::getNtfySettings($type, $user);
} }
throw new FireflyException(sprintf('Cannot handle channel "%s"', $channel)); throw new FireflyException(sprintf('Cannot handle channel "%s"', $channel));
} }
@ -54,7 +56,7 @@ class ReturnsSettings
$settings['ntfy_user'] = FireflyConfig::getEncrypted('ntfy_user', '')->data; $settings['ntfy_user'] = FireflyConfig::getEncrypted('ntfy_user', '')->data;
$settings['ntfy_pass'] = FireflyConfig::getEncrypted('ntfy_pass', '')->data; $settings['ntfy_pass'] = FireflyConfig::getEncrypted('ntfy_pass', '')->data;
} }
return $settings; return $settings;
} }
} }

View File

@ -49,8 +49,6 @@ class TestNotificationEmail extends Notification
/** /**
* Get the array representation of the notification. * Get the array representation of the notification.
* *
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
* *
* @return array * @return array
@ -73,9 +71,11 @@ class TestNotificationEmail extends Notification
public function toMail(OwnerNotifiable $notifiable) public function toMail(OwnerNotifiable $notifiable)
{ {
$address = (string) config('firefly.site_owner'); $address = (string) config('firefly.site_owner');
return (new MailMessage()) return (new MailMessage())
->markdown('emails.admin-test', ['email' => $address]) ->markdown('emails.admin-test', ['email' => $address])
->subject((string) trans('email.admin_test_subject')); ->subject((string) trans('email.admin_test_subject'))
;
} }
/** /**

View File

@ -26,13 +26,12 @@ namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable; use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Notifications\ReturnsSettings; use FireflyIII\Notifications\ReturnsSettings;
use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use Ntfy\Message; use Ntfy\Message;
use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel; use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel;
//use Illuminate\Notifications\Slack\SlackMessage; // use Illuminate\Notifications\Slack\SlackMessage;
/** /**
* Class TestNotification * Class TestNotification
@ -66,7 +65,6 @@ class TestNotificationNtfy extends Notification
]; ];
} }
public function toNtfy(OwnerNotifiable $notifiable): Message public function toNtfy(OwnerNotifiable $notifiable): Message
{ {
$settings = ReturnsSettings::getSettings('ntfy', 'owner', null); $settings = ReturnsSettings::getSettings('ntfy', 'owner', null);
@ -82,7 +80,7 @@ class TestNotificationNtfy extends Notification
config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]); config(['ntfy-notification-channel.authentication.password' => $settings['ntfy_pass']]);
} }
$message = new Message(); $message = new Message();
$message->topic($settings['ntfy_topic']); $message->topic($settings['ntfy_topic']);
$message->title((string) trans('email.admin_test_subject')); $message->title((string) trans('email.admin_test_subject'));
$message->body((string) trans('email.admin_test_message', ['channel' => 'ntfy'])); $message->body((string) trans('email.admin_test_message', ['channel' => 'ntfy']));

View File

@ -25,17 +25,13 @@ declare(strict_types=1);
namespace FireflyIII\Notifications\Test; namespace FireflyIII\Notifications\Test;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable; use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\User;
use Illuminate\Bus\Queueable; use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use NotificationChannels\Pushover\PushoverChannel; use NotificationChannels\Pushover\PushoverChannel;
use NotificationChannels\Pushover\PushoverMessage; use NotificationChannels\Pushover\PushoverMessage;
use Ntfy\Message;
use Wijourdil\NtfyNotificationChannel\Channels\NtfyChannel;
//use Illuminate\Notifications\Slack\SlackMessage; // use Illuminate\Notifications\Slack\SlackMessage;
/** /**
* Class TestNotification * Class TestNotification
@ -57,8 +53,6 @@ class TestNotificationPushover extends Notification
/** /**
* Get the array representation of the notification. * Get the array representation of the notification.
* *
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
* *
* @return array * @return array
@ -69,16 +63,15 @@ class TestNotificationPushover extends Notification
]; ];
} }
public function toPushover(OwnerNotifiable $notifiable): PushoverMessage public function toPushover(OwnerNotifiable $notifiable): PushoverMessage
{ {
Log::debug('Now in toPushover()'); Log::debug('Now in toPushover()');
return PushoverMessage::create((string)trans('email.admin_test_message', ['channel' => 'Pushover'])) return PushoverMessage::create((string)trans('email.admin_test_message', ['channel' => 'Pushover']))
->title((string)trans('email.admin_test_subject')); ->title((string)trans('email.admin_test_subject'))
;
} }
/** /**
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/ */

View File

@ -29,7 +29,7 @@ use Illuminate\Bus\Queueable;
use Illuminate\Notifications\Messages\SlackMessage; use Illuminate\Notifications\Messages\SlackMessage;
use Illuminate\Notifications\Notification; use Illuminate\Notifications\Notification;
//use Illuminate\Notifications\Slack\SlackMessage; // use Illuminate\Notifications\Slack\SlackMessage;
/** /**
* Class TestNotification * Class TestNotification
@ -51,8 +51,6 @@ class TestNotificationSlack extends Notification
/** /**
* Get the array representation of the notification. * Get the array representation of the notification.
* *
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
* *
* @return array * @return array
@ -66,15 +64,12 @@ class TestNotificationSlack extends Notification
/** /**
* Get the Slack representation of the notification. * Get the Slack representation of the notification.
* *
* @param OwnerNotifiable $notifiable
*
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
*
*/ */
public function toSlack(OwnerNotifiable $notifiable) public function toSlack(OwnerNotifiable $notifiable)
{ {
return new SlackMessage()->content((string) trans('email.admin_test_subject')); return new SlackMessage()->content((string) trans('email.admin_test_subject'));
//return new SlackMessage()->text((string) trans('email.admin_test_subject'))->to($url); // return new SlackMessage()->text((string) trans('email.admin_test_subject'))->to($url);
} }
/** /**
@ -82,8 +77,6 @@ class TestNotificationSlack extends Notification
* *
* @SuppressWarnings(PHPMD.UnusedFormalParameter) * @SuppressWarnings(PHPMD.UnusedFormalParameter)
* *
* @param OwnerNotifiable $notifiable
*
* @return array * @return array
*/ */
public function via(OwnerNotifiable $notifiable) public function via(OwnerNotifiable $notifiable)

View File

@ -25,7 +25,6 @@ namespace FireflyIII\Providers;
use FireflyIII\Events\ActuallyLoggedIn; use FireflyIII\Events\ActuallyLoggedIn;
use FireflyIII\Events\Admin\InvitationCreated; use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\AdminRequestedTestMessage;
use FireflyIII\Events\DestroyedTransactionGroup; use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\DetectedNewIPAddress; use FireflyIII\Events\DetectedNewIPAddress;
use FireflyIII\Events\Model\BudgetLimit\Created; use FireflyIII\Events\Model\BudgetLimit\Created;
@ -137,7 +136,7 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail', 'FireflyIII\Handlers\Events\UserEventHandler@sendEmailChangeUndoMail',
], ],
// admin related // admin related
TestNotificationChannel::class => [ TestNotificationChannel::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification', 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestNotification',
], ],
NewVersionAvailable::class => [ NewVersionAvailable::class => [
@ -147,7 +146,7 @@ class EventServiceProvider extends ServiceProvider
'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification', 'FireflyIII\Handlers\Events\AdminEventHandler@sendInvitationNotification',
'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite', 'FireflyIII\Handlers\Events\UserEventHandler@sendRegistrationInvite',
], ],
UnknownUserAttemptedLogin::class => [ UnknownUserAttemptedLogin::class => [
'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification', 'FireflyIII\Handlers\Events\AdminEventHandler@sendLoginAttemptNotification',
], ],

View File

@ -50,10 +50,10 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
*/ */
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string
{ {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
// same complex where query as below. // same complex where query as below.
->where( ->where(
static function (Builder $q5) use ($start, $end): void { static function (Builder $q5) use ($start, $end): void {
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { static function (Builder $q1) use ($start, $end): void {
@ -63,27 +63,30 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
} }
) )
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
); )
;
} }
) )
->orWhere( ->orWhere(
static function (Builder $q4) use ($start, $end): void { static function (Builder $q4) use ($start, $end): void {
// or start is before start AND end is after end. // or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
} }
); )
;
} }
) )
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.active', true) ->where('budgets.active', true)
->where('budgets.user_id', $this->user->id); ->where('budgets.user_id', $this->user->id)
;
if (null !== $budgets && $budgets->count() > 0) { if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray()); $query->whereIn('budget_limits.budget_id', $budgets->pluck('id')->toArray());
} }
@ -135,17 +138,19 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// both are NULL: // both are NULL:
if (null === $start && null === $end) { if (null === $start && null === $end) {
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->get(['budget_limits.*']); ->get(['budget_limits.*'])
;
} }
// one of the two is NULL. // one of the two is NULL.
if (null === $start xor null === $end) { if (null === $start xor null === $end) {
$query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') $query = BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where('budgets.user_id', $this->user->id); ->where('budgets.user_id', $this->user->id)
;
if (null !== $end) { if (null !== $end) {
// end date must be before $end. // end date must be before $end.
$query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00'));
@ -160,36 +165,39 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// neither are NULL: // neither are NULL:
return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id') return BudgetLimit::leftJoin('budgets', 'budgets.id', '=', 'budget_limits.budget_id')
->with(['budget']) ->with(['budget'])
->where('budgets.user_id', $this->user->id) ->where('budgets.user_id', $this->user->id)
->whereNull('budgets.deleted_at') ->whereNull('budgets.deleted_at')
->where( ->where(
static function (Builder $q5) use ($start, $end): void { static function (Builder $q5) use ($start, $end): void {
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { static function (Builder $q1) use ($start, $end): void {
$q1->where( $q1->where(
static function (Builder $q2) use ($start, $end): void { static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d')); $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d'));
} }
) )
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d'));
} }
); )
} ;
) }
->orWhere( )
static function (Builder $q4) use ($start, $end): void { ->orWhere(
// or start is before start AND end is after end. static function (Builder $q4) use ($start, $end): void {
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d')); // or start is before start AND end is after end.
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d'));
} $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d'));
); }
} )
)->get(['budget_limits.*']); ;
}
)->get(['budget_limits.*'])
;
} }
public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection public function getBudgetLimits(Budget $budget, ?Carbon $start = null, ?Carbon $end = null): Collection
@ -214,38 +222,41 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
// when both dates are set: // when both dates are set:
return $budget->budgetlimits() return $budget->budgetlimits()
->where( ->where(
static function (Builder $q5) use ($start, $end): void { // @phpstan-ignore-line static function (Builder $q5) use ($start, $end): void { // @phpstan-ignore-line
$q5->where( $q5->where(
static function (Builder $q1) use ($start, $end): void { static function (Builder $q1) use ($start, $end): void {
// budget limit ends within period // budget limit ends within period
$q1->where( $q1->where(
static function (Builder $q2) use ($start, $end): void { static function (Builder $q2) use ($start, $end): void {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59')); $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59'));
} }
) )
// budget limit start within period // budget limit start within period
->orWhere( ->orWhere(
static function (Builder $q3) use ($start, $end): void { static function (Builder $q3) use ($start, $end): void {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59')); $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
} }
); )
} ;
) }
->orWhere( )
static function (Builder $q4) use ($start, $end): void { ->orWhere(
// or start is before start AND end is after end. static function (Builder $q4) use ($start, $end): void {
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59')); // or start is before start AND end is after end.
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59'));
} $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
); }
} )
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); ;
}
)->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*'])
;
} }
public function setUser(null | Authenticatable | User $user): void public function setUser(null|Authenticatable|User $user): void
{ {
if ($user instanceof User) { if ($user instanceof User) {
$this->user = $user; $this->user = $user;
@ -259,33 +270,34 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
{ {
// if no currency has been provided, use the user's default currency: // if no currency has been provided, use the user's default currency:
/** @var TransactionCurrencyFactory $factory */ /** @var TransactionCurrencyFactory $factory */
$factory = app(TransactionCurrencyFactory::class); $factory = app(TransactionCurrencyFactory::class);
$currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null); $currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null);
if (null === $currency) { if (null === $currency) {
$currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); $currency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
} }
$currency->enabled = true; $currency->enabled = true;
$currency->save(); $currency->save();
// find the budget: // find the budget:
$budget = $this->user->budgets()->find((int) $data['budget_id']); $budget = $this->user->budgets()->find((int) $data['budget_id']);
if (null === $budget) { if (null === $budget) {
throw new FireflyException('200004: Budget does not exist.'); throw new FireflyException('200004: Budget does not exist.');
} }
// find limit with same date range and currency. // find limit with same date range and currency.
$limit = $budget->budgetlimits() $limit = $budget->budgetlimits()
->where('budget_limits.start_date', $data['start_date']->format('Y-m-d')) ->where('budget_limits.start_date', $data['start_date']->format('Y-m-d'))
->where('budget_limits.end_date', $data['end_date']->format('Y-m-d')) ->where('budget_limits.end_date', $data['end_date']->format('Y-m-d'))
->where('budget_limits.transaction_currency_id', $currency->id) ->where('budget_limits.transaction_currency_id', $currency->id)
->first(['budget_limits.*']); ->first(['budget_limits.*'])
;
if (null !== $limit) { if (null !== $limit) {
throw new FireflyException('200027: Budget limit already exists.'); throw new FireflyException('200027: Budget limit already exists.');
} }
app('log')->debug('No existing budget limit, create a new one'); app('log')->debug('No existing budget limit, create a new one');
// or create one and return it. // or create one and return it.
$limit = new BudgetLimit(); $limit = new BudgetLimit();
$limit->budget()->associate($budget); $limit->budget()->associate($budget);
$limit->start_date = $data['start_date']->format('Y-m-d'); $limit->start_date = $data['start_date']->format('Y-m-d');
$limit->end_date = $data['end_date']->format('Y-m-d'); $limit->end_date = $data['end_date']->format('Y-m-d');
@ -293,7 +305,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$limit->transaction_currency_id = $currency->id; $limit->transaction_currency_id = $currency->id;
$limit->save(); $limit->save();
$noteText = (string) ($data['notes'] ?? ''); $noteText = (string) ($data['notes'] ?? '');
if ('' !== $noteText) { if ('' !== $noteText) {
$this->setNoteText($limit, $noteText); $this->setNoteText($limit, $noteText);
} }
@ -303,13 +315,13 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
return $limit; return $limit;
} }
public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit public function find(Budget $budget, TransactionCurrency $currency, Carbon $start, Carbon $end): ?BudgetLimit
{ {
return $budget->budgetlimits() return $budget->budgetlimits()
->where('transaction_currency_id', $currency->id) ->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d')) ->where('start_date', $start->format('Y-m-d'))
->where('end_date', $end->format('Y-m-d'))->first(); ->where('end_date', $end->format('Y-m-d'))->first()
;
} }
/** /**
@ -317,8 +329,8 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
*/ */
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit
{ {
$budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount; $budgetLimit->amount = array_key_exists('amount', $data) ? $data['amount'] : $budgetLimit->amount;
$budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id; $budgetLimit->budget_id = array_key_exists('budget_id', $data) ? $data['budget_id'] : $budgetLimit->budget_id;
if (array_key_exists('start', $data)) { if (array_key_exists('start', $data)) {
$budgetLimit->start_date = $data['start']->startOfDay(); $budgetLimit->start_date = $data['start']->startOfDay();
@ -330,7 +342,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
} }
// if no currency has been provided, use the user's default currency: // if no currency has been provided, use the user's default currency:
$currency = null; $currency = null;
// update if relevant: // update if relevant:
if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) { if (array_key_exists('currency_id', $data) || array_key_exists('currency_code', $data)) {
@ -342,14 +354,14 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
if (null === $currency) { if (null === $currency) {
$currency = $budgetLimit->transactionCurrency ?? app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); $currency = $budgetLimit->transactionCurrency ?? app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
} }
$currency->enabled = true; $currency->enabled = true;
$currency->save(); $currency->save();
$budgetLimit->transaction_currency_id = $currency->id; $budgetLimit->transaction_currency_id = $currency->id;
$budgetLimit->save(); $budgetLimit->save();
// update notes if they exist. // update notes if they exist.
if(array_key_exists('notes', $data)) { if (array_key_exists('notes', $data)) {
$this->setNoteText($budgetLimit, (string)$data['notes']); $this->setNoteText($budgetLimit, (string)$data['notes']);
} }
@ -359,26 +371,29 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit public function updateLimitAmount(Budget $budget, Carbon $start, Carbon $end, string $amount): ?BudgetLimit
{ {
// count the limits: // count the limits:
$limits = $budget->budgetlimits() $limits = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->count('budget_limits.*'); ->count('budget_limits.*')
;
app('log')->debug(sprintf('Found %d budget limits.', $limits)); app('log')->debug(sprintf('Found %d budget limits.', $limits));
// there might be a budget limit for these dates: // there might be a budget limit for these dates:
/** @var null|BudgetLimit $limit */ /** @var null|BudgetLimit $limit */
$limit = $budget->budgetlimits() $limit = $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->first(['budget_limits.*']); ->first(['budget_limits.*'])
;
// if more than 1 limit found, delete the others: // if more than 1 limit found, delete the others:
if ($limits > 1 && null !== $limit) { if ($limits > 1 && null !== $limit) {
app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id)); app('log')->debug(sprintf('Found more than 1, delete all except #%d', $limit->id));
$budget->budgetlimits() $budget->budgetlimits()
->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00')) ->where('budget_limits.start_date', $start->format('Y-m-d 00:00:00'))
->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00')) ->where('budget_limits.end_date', $end->format('Y-m-d 00:00:00'))
->where('budget_limits.id', '!=', $limit->id)->delete(); ->where('budget_limits.id', '!=', $limit->id)->delete()
;
} }
// delete if amount is zero. // delete if amount is zero.
@ -400,7 +415,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
} }
app('log')->debug('No existing budget limit, create a new one'); app('log')->debug('No existing budget limit, create a new one');
// or create one and return it. // or create one and return it.
$limit = new BudgetLimit(); $limit = new BudgetLimit();
$limit->budget()->associate($budget); $limit->budget()->associate($budget);
$limit->start_date = $start->startOfDay(); $limit->start_date = $start->startOfDay();
$limit->start_date_tz = $start->format('e'); $limit->start_date_tz = $start->format('e');
@ -413,12 +428,14 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
return $limit; return $limit;
} }
#[\Override] public function getNoteText(BudgetLimit $budgetLimit): string #[\Override]
public function getNoteText(BudgetLimit $budgetLimit): string
{ {
return (string) $budgetLimit->notes()->first()?->text; return (string) $budgetLimit->notes()->first()?->text;
} }
#[\Override] public function setNoteText(BudgetLimit $budgetLimit, string $text): void #[\Override]
public function setNoteText(BudgetLimit $budgetLimit, string $text): void
{ {
$dbNote = $budgetLimit->notes()->first(); $dbNote = $budgetLimit->notes()->first();
if ('' !== $text) { if ('' !== $text) {

View File

@ -49,6 +49,7 @@ interface BudgetLimitRepositoryInterface
public function destroyAll(): void; public function destroyAll(): void;
public function getNoteText(BudgetLimit $budgetLimit): string; public function getNoteText(BudgetLimit $budgetLimit): string;
public function setNoteText(BudgetLimit $budgetLimit, string $text): void; public function setNoteText(BudgetLimit $budgetLimit, string $text): void;
/** /**

View File

@ -106,7 +106,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface
} }
} }
#[\Override] public function find(int $currencyId): ?TransactionCurrency #[\Override]
public function find(int $currencyId): ?TransactionCurrency
{ {
return TransactionCurrency::find($currencyId); return TransactionCurrency::find($currencyId);
} }

View File

@ -30,11 +30,9 @@ use FireflyIII\Factory\PiggyBankFactory;
use FireflyIII\Models\Note; use FireflyIII\Models\Note;
use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\PiggyBankRepetition; use FireflyIII\Models\PiggyBankRepetition;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups; use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Amount;
use Illuminate\Database\QueryException;
/** /**
* Trait ModifiesPiggyBanks * Trait ModifiesPiggyBanks
@ -59,7 +57,7 @@ trait ModifiesPiggyBanks
public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool public function removeAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool
{ {
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null === $repetition) { if (null === $repetition) {
return false; return false;
} }
@ -74,11 +72,11 @@ trait ModifiesPiggyBanks
public function addAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool public function addAmount(PiggyBank $piggyBank, string $amount, ?TransactionJournal $journal = null): bool
{ {
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null === $repetition) { if (null === $repetition) {
return false; return false;
} }
$currentAmount = $repetition->current_amount ?? '0'; $currentAmount = $repetition->current_amount ?? '0';
$repetition->current_amount = bcadd($currentAmount, $amount); $repetition->current_amount = bcadd($currentAmount, $amount);
$repetition->save(); $repetition->save();
@ -143,15 +141,15 @@ trait ModifiesPiggyBanks
public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank public function setCurrentAmount(PiggyBank $piggyBank, string $amount): PiggyBank
{ {
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null === $repetition) { if (null === $repetition) {
return $piggyBank; return $piggyBank;
} }
$max = $piggyBank->target_amount; $max = $piggyBank->target_amount;
if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) { if (1 === bccomp($amount, $max) && 0 !== bccomp($piggyBank->target_amount, '0')) {
$amount = $max; $amount = $max;
} }
$difference = bcsub($amount, $repetition->current_amount); $difference = bcsub($amount, $repetition->current_amount);
$repetition->current_amount = $amount; $repetition->current_amount = $amount;
$repetition->save(); $repetition->save();
@ -184,10 +182,10 @@ trait ModifiesPiggyBanks
{ {
$factory = new PiggyBankFactory(); $factory = new PiggyBankFactory();
$factory->user = $this->user; $factory->user = $this->user;
return $factory->store($data); return $factory->store($data);
} }
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool public function setOrder(PiggyBank $piggyBank, int $newOrder): bool
{ {
$oldOrder = $piggyBank->order; $oldOrder = $piggyBank->order;
@ -220,7 +218,8 @@ trait ModifiesPiggyBanks
if ('' === $note) { if ('' === $note) {
$dbNote = $piggyBank->notes()->first(); $dbNote = $piggyBank->notes()->first();
$dbNote?->delete(); $dbNote?->delete();
return ;
return;
} }
$dbNote = $piggyBank->notes()->first(); $dbNote = $piggyBank->notes()->first();
if (null === $dbNote) { if (null === $dbNote) {
@ -249,7 +248,7 @@ trait ModifiesPiggyBanks
// remove money from the rep. // remove money from the rep.
$repetition = $this->getRepetition($piggyBank); $repetition = $this->getRepetition($piggyBank);
if (null !== $repetition && $repetition->current_amount > $piggyBank->target_amount && 0 !== bccomp($piggyBank->target_amount, '0')) { if (null !== $repetition && $repetition->current_amount > $piggyBank->target_amount && 0 !== bccomp($piggyBank->target_amount, '0')) {
$difference = bcsub($piggyBank->target_amount, $repetition->current_amount); $difference = bcsub($piggyBank->target_amount, $repetition->current_amount);
// an amount will be removed, create "negative" event: // an amount will be removed, create "negative" event:
event(new ChangedAmount($piggyBank, $difference, null, null)); event(new ChangedAmount($piggyBank, $difference, null, null));

View File

@ -95,7 +95,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function getAttachments(PiggyBank $piggyBank): Collection public function getAttachments(PiggyBank $piggyBank): Collection
{ {
$set = $piggyBank->attachments()->get(); $set = $piggyBank->attachments()->get();
/** @var \Storage $disk */ /** @var \Storage $disk */
$disk = \Storage::disk('upload'); $disk = \Storage::disk('upload');
@ -122,12 +122,14 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$amount = '' === $amount ? '0' : $amount; $amount = '' === $amount ? '0' : $amount;
$sum = bcadd($sum, $amount); $sum = bcadd($sum, $amount);
} }
return $sum; return $sum;
} }
public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition public function getRepetition(PiggyBank $piggyBank): ?PiggyBankRepetition
{ {
throw new FireflyException('[b] Piggy bank repetitions are EOL.'); throw new FireflyException('[b] Piggy bank repetitions are EOL.');
return $piggyBank->piggyBankRepetitions()->first(); return $piggyBank->piggyBankRepetitions()->first();
} }
@ -146,15 +148,15 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
throw new FireflyException('[c] Piggy bank repetitions are EOL.'); throw new FireflyException('[c] Piggy bank repetitions are EOL.');
app('log')->debug(sprintf('Now in getExactAmount(%d, %d, %d)', $piggyBank->id, $repetition->id, $journal->id)); app('log')->debug(sprintf('Now in getExactAmount(%d, %d, %d)', $piggyBank->id, $repetition->id, $journal->id));
$operator = null; $operator = null;
$currency = null; $currency = null;
/** @var JournalRepositoryInterface $journalRepost */ /** @var JournalRepositoryInterface $journalRepost */
$journalRepost = app(JournalRepositoryInterface::class); $journalRepost = app(JournalRepositoryInterface::class);
$journalRepost->setUser($this->user); $journalRepost->setUser($this->user);
/** @var AccountRepositoryInterface $accountRepos */ /** @var AccountRepositoryInterface $accountRepos */
$accountRepos = app(AccountRepositoryInterface::class); $accountRepos = app(AccountRepositoryInterface::class);
$accountRepos->setUser($this->user); $accountRepos->setUser($this->user);
$defaultCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup); $defaultCurrency = app('amount')->getDefaultCurrencyByUserGroup($this->user->userGroup);
@ -163,10 +165,10 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
app('log')->debug(sprintf('Piggy bank #%d currency is %s', $piggyBank->id, $piggyBankCurrency->code)); app('log')->debug(sprintf('Piggy bank #%d currency is %s', $piggyBank->id, $piggyBankCurrency->code));
/** @var Transaction $source */ /** @var Transaction $source */
$source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first(); $source = $journal->transactions()->with(['account'])->where('amount', '<', 0)->first();
/** @var Transaction $destination */ /** @var Transaction $destination */
$destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first(); $destination = $journal->transactions()->with(['account'])->where('amount', '>', 0)->first();
// matches source, which means amount will be removed from piggy: // matches source, which means amount will be removed from piggy:
if ($source->account_id === $piggyBank->account_id) { if ($source->account_id === $piggyBank->account_id) {
@ -188,7 +190,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
} }
// currency of the account + the piggy bank currency are almost the same. // currency of the account + the piggy bank currency are almost the same.
// which amount from the transaction matches? // which amount from the transaction matches?
$amount = null; $amount = null;
if ((int) $source->transaction_currency_id === $currency->id) { if ((int) $source->transaction_currency_id === $currency->id) {
app('log')->debug('Use normal amount'); app('log')->debug('Use normal amount');
$amount = app('steam')->{$operator}($source->amount); // @phpstan-ignore-line $amount = app('steam')->{$operator}($source->amount); // @phpstan-ignore-line
@ -204,8 +206,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
} }
app('log')->debug(sprintf('The currency is %s and the amount is %s', $currency->code, $amount)); app('log')->debug(sprintf('The currency is %s and the amount is %s', $currency->code, $amount));
$room = bcsub($piggyBank->target_amount, $repetition->current_amount); $room = bcsub($piggyBank->target_amount, $repetition->current_amount);
$compare = bcmul($repetition->current_amount, '-1'); $compare = bcmul($repetition->current_amount, '-1');
if (0 === bccomp($piggyBank->target_amount, '0')) { if (0 === bccomp($piggyBank->target_amount, '0')) {
// amount is zero? then the "room" is positive amount of we wish to add or remove. // amount is zero? then the "room" is positive amount of we wish to add or remove.
@ -237,14 +239,13 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
return (string) $amount; return (string) $amount;
} }
public function setUser(null | Authenticatable | User $user): void public function setUser(null|Authenticatable|User $user): void
{ {
if ($user instanceof User) { if ($user instanceof User) {
$this->user = $user; $this->user = $user;
} }
} }
/** /**
* Return note for piggy bank. * Return note for piggy bank.
*/ */
@ -263,12 +264,12 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
{ {
$currency = app('amount')->getDefaultCurrency(); $currency = app('amount')->getDefaultCurrency();
$set = $this->getPiggyBanks(); $set = $this->getPiggyBanks();
/** @var PiggyBank $piggy */ /** @var PiggyBank $piggy */
foreach ($set as $piggy) { foreach ($set as $piggy) {
$currentAmount = $this->getRepetition($piggy)->current_amount ?? '0'; $currentAmount = $this->getRepetition($piggy)->current_amount ?? '0';
$piggy->name = $piggy->name . ' (' . app('amount')->formatAnything($currency, $currentAmount, false) . ')'; $piggy->name = $piggy->name.' ('.app('amount')->formatAnything($currency, $currentAmount, false).')';
} }
return $set; return $set;
@ -276,8 +277,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
public function getPiggyBanks(): Collection public function getPiggyBanks(): Collection
{ {
return PiggyBank return PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', auth()->user()->id) ->where('accounts.user_id', auth()->user()->id)
->with( ->with(
@ -286,7 +286,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
'objectGroups', 'objectGroups',
] ]
) )
->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*']); ->orderBy('piggy_banks.order', 'ASC')->get(['piggy_banks.*'])
;
} }
/** /**
@ -344,17 +345,20 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface
$search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query)); $search->whereLike('piggy_banks.name', sprintf('%%%s%%', $query));
} }
$search->orderBy('piggy_banks.order', 'ASC') $search->orderBy('piggy_banks.order', 'ASC')
->orderBy('piggy_banks.name', 'ASC'); ->orderBy('piggy_banks.name', 'ASC')
;
return $search->take($limit)->get(); return $search->take($limit)->get();
} }
#[\Override] public function purgeAll(): void #[\Override]
public function purgeAll(): void
{ {
throw new FireflyException('TODO Not implemented'); throw new FireflyException('TODO Not implemented');
} }
#[\Override] public function resetOrder(): void #[\Override]
public function resetOrder(): void
{ {
$factory = new PiggyBankFactory(); $factory = new PiggyBankFactory();
$factory->user = $this->user; $factory->user = $this->user;

View File

@ -129,7 +129,7 @@ interface PiggyBankRepositoryInterface
*/ */
public function setOrder(PiggyBank $piggyBank, int $newOrder): bool; public function setOrder(PiggyBank $piggyBank, int $newOrder): bool;
public function setUser(null | Authenticatable | User $user): void; public function setUser(null|Authenticatable|User $user): void;
/** /**
* Store new piggy bank. * Store new piggy bank.

View File

@ -18,11 +18,11 @@ class IsValidDiscordUrl implements ValidationRule
public function validate(string $attribute, mixed $value, \Closure $fail): void public function validate(string $attribute, mixed $value, \Closure $fail): void
{ {
$value = (string)$value; $value = (string)$value;
if('' === $value) { if ('' === $value) {
return; return;
} }
if(!str_starts_with($value, 'https://discord.com/api/webhooks/')) { if (!str_starts_with($value, 'https://discord.com/api/webhooks/')) {
$fail('validation.active_url')->translate(); $fail('validation.active_url')->translate();
$message = sprintf('IsValidDiscordUrl: "%s" is not a discord URL.', substr($value, 0, 255)); $message = sprintf('IsValidDiscordUrl: "%s" is not a discord URL.', substr($value, 0, 255));
Log::debug($message); Log::debug($message);

View File

@ -18,11 +18,11 @@ class IsValidSlackOrDiscordUrl implements ValidationRule
public function validate(string $attribute, mixed $value, \Closure $fail): void public function validate(string $attribute, mixed $value, \Closure $fail): void
{ {
$value = (string)$value; $value = (string)$value;
if('' === $value) { if ('' === $value) {
return; return;
} }
if(!str_starts_with($value, 'https://hooks.slack.com/services/') && !str_starts_with($value, 'https://discord.com/api/webhooks/')) { if (!str_starts_with($value, 'https://hooks.slack.com/services/') && !str_starts_with($value, 'https://discord.com/api/webhooks/')) {
$fail('validation.active_url')->translate(); $fail('validation.active_url')->translate();
$message = sprintf('IsValidSlackUrl: "%s" is not a discord or slack URL.', substr($value, 0, 255)); $message = sprintf('IsValidSlackUrl: "%s" is not a discord or slack URL.', substr($value, 0, 255));
Log::debug($message); Log::debug($message);

View File

@ -18,11 +18,11 @@ class IsValidSlackUrl implements ValidationRule
public function validate(string $attribute, mixed $value, \Closure $fail): void public function validate(string $attribute, mixed $value, \Closure $fail): void
{ {
$value = (string)$value; $value = (string)$value;
if('' === $value) { if ('' === $value) {
return; return;
} }
if(!str_starts_with($value, 'https://hooks.slack.com/services/')) { if (!str_starts_with($value, 'https://hooks.slack.com/services/')) {
$fail('validation.active_url')->translate(); $fail('validation.active_url')->translate();
$message = sprintf('IsValidSlackUrl: "%s" is not a slack URL.', substr($value, 0, 255)); $message = sprintf('IsValidSlackUrl: "%s" is not a slack URL.', substr($value, 0, 255));
Log::debug($message); Log::debug($message);

View File

@ -289,6 +289,7 @@ class ExpandedForm
return $html; return $html;
} }
/** /**
* @throws FireflyException * @throws FireflyException
*/ */
@ -299,7 +300,7 @@ class ExpandedForm
$classes = $this->getHolderClasses($name); $classes = $this->getHolderClasses($name);
try { try {
$html = view('form.password', compact('classes', 'value','name', 'label', 'options'))->render(); $html = view('form.password', compact('classes', 'value', 'name', 'label', 'options'))->render();
} catch (\Throwable $e) { } catch (\Throwable $e) {
app('log')->debug(sprintf('Could not render passwordWithValue(): %s', $e->getMessage())); app('log')->debug(sprintf('Could not render passwordWithValue(): %s', $e->getMessage()));
$html = 'Could not render passwordWithValue.'; $html = 'Could not render passwordWithValue.';

View File

@ -37,7 +37,7 @@ class FireflyConfig
{ {
public function delete(string $name): void public function delete(string $name): void
{ {
$fullName = 'ff-config-' . $name; $fullName = 'ff-config-'.$name;
if (\Cache::has($fullName)) { if (\Cache::has($fullName)) {
\Cache::forget($fullName); \Cache::forget($fullName);
} }
@ -57,14 +57,18 @@ class FireflyConfig
} }
if ('' === $result->data) { if ('' === $result->data) {
Log::warning(sprintf('Empty encrypted configuration value found: "%s"', $name)); Log::warning(sprintf('Empty encrypted configuration value found: "%s"', $name));
return $result; return $result;
} }
try { try {
$result->data = decrypt($result->data); $result->data = decrypt($result->data);
} catch (DecryptException $e) { } catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt configuration value "%s": %s', $name, $e->getMessage())); Log::error(sprintf('Could not decrypt configuration value "%s": %s', $name, $e->getMessage()));
return $result; return $result;
} }
return $result; return $result;
} }
@ -75,7 +79,7 @@ class FireflyConfig
*/ */
public function get(string $name, $default = null): ?Configuration public function get(string $name, $default = null): ?Configuration
{ {
$fullName = 'ff-config-' . $name; $fullName = 'ff-config-'.$name;
if (\Cache::has($fullName)) { if (\Cache::has($fullName)) {
return \Cache::get($fullName); return \Cache::get($fullName);
} }
@ -83,7 +87,7 @@ class FireflyConfig
try { try {
/** @var null|Configuration $config */ /** @var null|Configuration $config */
$config = Configuration::where('name', $name)->first(['id', 'name', 'data']); $config = Configuration::where('name', $name)->first(['id', 'name', 'data']);
} catch (\Exception | QueryException $e) { } catch (\Exception|QueryException $e) {
throw new FireflyException(sprintf('Could not poll the database: %s', $e->getMessage()), 0, $e); throw new FireflyException(sprintf('Could not poll the database: %s', $e->getMessage()), 0, $e);
} }
@ -106,8 +110,10 @@ class FireflyConfig
$encrypted = encrypt($value); $encrypted = encrypt($value);
} catch (EncryptException $e) { } catch (EncryptException $e) {
Log::error(sprintf('Could not encrypt configuration value "%s": %s', $name, $e->getMessage())); Log::error(sprintf('Could not encrypt configuration value "%s": %s', $name, $e->getMessage()));
throw new FireflyException(sprintf('Could not encrypt configuration value "%s". Cowardly refuse to continue.', $name)); throw new FireflyException(sprintf('Could not encrypt configuration value "%s". Cowardly refuse to continue.', $name));
} }
return $this->set($name, $encrypted); return $this->set($name, $encrypted);
} }
@ -129,13 +135,13 @@ class FireflyConfig
$item->name = $name; $item->name = $name;
$item->data = $value; $item->data = $value;
$item->save(); $item->save();
\Cache::forget('ff-config-' . $name); \Cache::forget('ff-config-'.$name);
return $item; return $item;
} }
$config->data = $value; $config->data = $value;
$config->save(); $config->save();
\Cache::forget('ff-config-' . $name); \Cache::forget('ff-config-'.$name);
return $config; return $config;
} }

View File

@ -155,8 +155,9 @@ class AccountForm
*/ */
public function assetLiabilityMultiAccountList(string $name, $value = null, ?array $options = null): string public function assetLiabilityMultiAccountList(string $name, $value = null, ?array $options = null): string
{ {
$types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value,AccountTypeEnum::LOAN->value]; $types = [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value];
$grouped = $this->getAccountsGrouped($types); $grouped = $this->getAccountsGrouped($types);
return $this->multiSelect($name, $grouped, $value, $options); return $this->multiSelect($name, $grouped, $value, $options);
} }

View File

@ -64,14 +64,18 @@ class Preferences
} }
if ('' === $result->data) { if ('' === $result->data) {
Log::warning(sprintf('Empty encrypted preference found: "%s"', $name)); Log::warning(sprintf('Empty encrypted preference found: "%s"', $name));
return $result; return $result;
} }
try { try {
$result->data = decrypt($result->data); $result->data = decrypt($result->data);
} catch (DecryptException $e) { } catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage())); Log::error(sprintf('Could not decrypt preference "%s": %s', $name, $e->getMessage()));
return $result; return $result;
} }
return $result; return $result;
} }
@ -81,12 +85,13 @@ class Preferences
$encrypted = encrypt($value); $encrypted = encrypt($value);
} catch (EncryptException $e) { } catch (EncryptException $e) {
Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage())); Log::error(sprintf('Could not encrypt preference "%s": %s', $name, $e->getMessage()));
throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name)); throw new FireflyException(sprintf('Could not encrypt preference "%s". Cowardly refuse to continue.', $name));
} }
return $this->set($name, $encrypted); return $this->set($name, $encrypted);
} }
public function get(string $name, null|array|bool|int|string $default = null): ?Preference public function get(string $name, null|array|bool|int|string $default = null): ?Preference
{ {
/** @var null|User $user */ /** @var null|User $user */

View File

@ -55,8 +55,8 @@ class BudgetLimitTransformer extends AbstractTransformer
*/ */
public function transform(BudgetLimit $budgetLimit): array public function transform(BudgetLimit $budgetLimit): array
{ {
$repository = app(OperationsRepository::class); $repository = app(OperationsRepository::class);
$limitRepos = app(BudgetLimitRepositoryInterface::class); $limitRepos = app(BudgetLimitRepositoryInterface::class);
$repository->setUser($budgetLimit->budget->user); $repository->setUser($budgetLimit->budget->user);
$limitRepos->setUser($budgetLimit->budget->user); $limitRepos->setUser($budgetLimit->budget->user);
$expenses = $repository->sumExpenses( $expenses = $repository->sumExpenses(
@ -82,7 +82,7 @@ class BudgetLimitTransformer extends AbstractTransformer
$currencySymbol = $currency->symbol; $currencySymbol = $currency->symbol;
$currencyDecimalPlaces = $currency->decimal_places; $currencyDecimalPlaces = $currency->decimal_places;
} }
$amount = app('steam')->bcround($amount, $currencyDecimalPlaces); $amount = app('steam')->bcround($amount, $currencyDecimalPlaces);
return [ return [
'id' => (string) $budgetLimit->id, 'id' => (string) $budgetLimit->id,
@ -103,7 +103,7 @@ class BudgetLimitTransformer extends AbstractTransformer
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',
'uri' => '/budgets/limits/' . $budgetLimit->id, 'uri' => '/budgets/limits/'.$budgetLimit->id,
], ],
], ],
]; ];

View File

@ -54,22 +54,22 @@ class PiggyBankTransformer extends AbstractTransformer
*/ */
public function transform(PiggyBank $piggyBank): array public function transform(PiggyBank $piggyBank): array
{ {
$user = $piggyBank->accounts()->first()->user; $user = $piggyBank->accounts()->first()->user;
// set up repositories // set up repositories
$this->accountRepos->setUser($user); $this->accountRepos->setUser($user);
$this->piggyRepos->setUser($user); $this->piggyRepos->setUser($user);
// note // note
$notes = $this->piggyRepos->getNoteText($piggyBank); $notes = $this->piggyRepos->getNoteText($piggyBank);
$notes = '' === $notes ? null : $notes; $notes = '' === $notes ? null : $notes;
$objectGroupId = null; $objectGroupId = null;
$objectGroupOrder = null; $objectGroupOrder = null;
$objectGroupTitle = null; $objectGroupTitle = null;
/** @var null|ObjectGroup $objectGroup */ /** @var null|ObjectGroup $objectGroup */
$objectGroup = $piggyBank->objectGroups->first(); $objectGroup = $piggyBank->objectGroups->first();
if (null !== $objectGroup) { if (null !== $objectGroup) {
$objectGroupId = $objectGroup->id; $objectGroupId = $objectGroup->id;
$objectGroupOrder = $objectGroup->order; $objectGroupOrder = $objectGroup->order;
@ -77,14 +77,14 @@ class PiggyBankTransformer extends AbstractTransformer
} }
// get currently saved amount: // get currently saved amount:
$currency = $piggyBank->transactionCurrency; $currency = $piggyBank->transactionCurrency;
$currentAmount = app('steam')->bcround($this->piggyRepos->getCurrentAmount($piggyBank), $currency->decimal_places); $currentAmount = app('steam')->bcround($this->piggyRepos->getCurrentAmount($piggyBank), $currency->decimal_places);
// Amounts, depending on 0.0 state of target amount // Amounts, depending on 0.0 state of target amount
$percentage = null; $percentage = null;
$targetAmount = $piggyBank->target_amount; $targetAmount = $piggyBank->target_amount;
$leftToSave = null; $leftToSave = null;
$savePerMonth = null; $savePerMonth = null;
if (0 !== bccomp($targetAmount, '0')) { // target amount is not 0.00 if (0 !== bccomp($targetAmount, '0')) { // target amount is not 0.00
$leftToSave = bcsub($piggyBank->target_amount, $currentAmount); $leftToSave = bcsub($piggyBank->target_amount, $currentAmount);
$percentage = (int) bcmul(bcdiv($currentAmount, $targetAmount), '100'); $percentage = (int) bcmul(bcdiv($currentAmount, $targetAmount), '100');
@ -92,16 +92,16 @@ class PiggyBankTransformer extends AbstractTransformer
$leftToSave = app('steam')->bcround($leftToSave, $currency->decimal_places); $leftToSave = app('steam')->bcround($leftToSave, $currency->decimal_places);
$savePerMonth = app('steam')->bcround($this->piggyRepos->getSuggestedMonthlyAmount($piggyBank), $currency->decimal_places); $savePerMonth = app('steam')->bcround($this->piggyRepos->getSuggestedMonthlyAmount($piggyBank), $currency->decimal_places);
} }
$startDate = $piggyBank->start_date?->format('Y-m-d'); $startDate = $piggyBank->start_date?->format('Y-m-d');
$targetDate = $piggyBank->target_date?->format('Y-m-d'); $targetDate = $piggyBank->target_date?->format('Y-m-d');
return [ return [
'id' => (string) $piggyBank->id, 'id' => (string) $piggyBank->id,
'created_at' => $piggyBank->created_at->toAtomString(), 'created_at' => $piggyBank->created_at->toAtomString(),
'updated_at' => $piggyBank->updated_at->toAtomString(), 'updated_at' => $piggyBank->updated_at->toAtomString(),
'accounts' => $this->renderAccounts($piggyBank), 'accounts' => $this->renderAccounts($piggyBank),
//'account_id' => (string)$piggyBank->account_id, // 'account_id' => (string)$piggyBank->account_id,
//'account_name' => $piggyBank->account->name, // 'account_name' => $piggyBank->account->name,
'name' => $piggyBank->name, 'name' => $piggyBank->name,
'currency_id' => (string) $currency->id, 'currency_id' => (string) $currency->id,
'currency_code' => $currency->code, 'currency_code' => $currency->code,
@ -123,7 +123,7 @@ class PiggyBankTransformer extends AbstractTransformer
'links' => [ 'links' => [
[ [
'rel' => 'self', 'rel' => 'self',
'uri' => '/piggy_banks/' . $piggyBank->id, 'uri' => '/piggy_banks/'.$piggyBank->id,
], ],
], ],
]; ];
@ -134,12 +134,13 @@ class PiggyBankTransformer extends AbstractTransformer
$return = []; $return = [];
foreach ($piggyBank->accounts()->get() as $account) { foreach ($piggyBank->accounts()->get() as $account) {
$return[] = [ $return[] = [
'id' => $account->id, 'id' => $account->id,
'name' => $account->name, 'name' => $account->name,
'current_amount' => $account->pivot->current_amount, 'current_amount' => $account->pivot->current_amount,
// TODO add balance, add left to save. // TODO add balance, add left to save.
]; ];
} }
return $return; return $return;
} }
} }

View File

@ -115,6 +115,7 @@ class PiggyBankTransformer extends AbstractTransformer
// grab repetitions (for current amount): // grab repetitions (for current amount):
$repetitions = PiggyBankRepetition::whereIn('piggy_bank_id', $piggyBanks)->get(); $repetitions = PiggyBankRepetition::whereIn('piggy_bank_id', $piggyBanks)->get();
throw new FireflyException('[d] Piggy bank repetitions are EOL.'); throw new FireflyException('[d] Piggy bank repetitions are EOL.');
/** @var PiggyBankRepetition $repetition */ /** @var PiggyBankRepetition $repetition */

View File

@ -49,10 +49,8 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup; use FireflyIII\Models\UserGroup;
use FireflyIII\Models\UserRole; use FireflyIII\Models\UserRole;
use FireflyIII\Models\Webhook; use FireflyIII\Models\Webhook;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Admin\UserRegistration; use FireflyIII\Notifications\Admin\UserRegistration;
use FireflyIII\Notifications\Admin\VersionCheckResult; use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Test\TestNotificationDiscord;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
@ -260,38 +258,38 @@ class User extends Authenticatable
app('log')->debug(sprintf('in hasAnyRoleInGroup(%s)', implode(', ', $roles))); app('log')->debug(sprintf('in hasAnyRoleInGroup(%s)', implode(', ', $roles)));
/** @var Collection $dbRoles */ /** @var Collection $dbRoles */
$dbRoles = UserRole::whereIn('title', $roles)->get(); $dbRoles = UserRole::whereIn('title', $roles)->get();
if (0 === $dbRoles->count()) { if (0 === $dbRoles->count()) {
app('log')->error(sprintf('Could not find role(s): %s. Probably migration mishap.', implode(', ', $roles))); app('log')->error(sprintf('Could not find role(s): %s. Probably migration mishap.', implode(', ', $roles)));
return false; return false;
} }
$dbRolesIds = $dbRoles->pluck('id')->toArray(); $dbRolesIds = $dbRoles->pluck('id')->toArray();
$dbRolesTitles = $dbRoles->pluck('title')->toArray(); $dbRolesTitles = $dbRoles->pluck('title')->toArray();
/** @var Collection $groupMemberships */ /** @var Collection $groupMemberships */
$groupMemberships = $this->groupMemberships()->whereIn('user_role_id', $dbRolesIds)->where('user_group_id', $userGroup->id)->get(); $groupMemberships = $this->groupMemberships()->whereIn('user_role_id', $dbRolesIds)->where('user_group_id', $userGroup->id)->get();
if (0 === $groupMemberships->count()) { if (0 === $groupMemberships->count()) {
app('log')->error(sprintf( app('log')->error(sprintf(
'User #%d "%s" does not have roles %s in user group #%d "%s"', 'User #%d "%s" does not have roles %s in user group #%d "%s"',
$this->id, $this->id,
$this->email, $this->email,
implode(', ', $roles), implode(', ', $roles),
$userGroup->id, $userGroup->id,
$userGroup->title $userGroup->title
)); ));
return false; return false;
} }
foreach ($groupMemberships as $membership) { foreach ($groupMemberships as $membership) {
app('log')->debug(sprintf( app('log')->debug(sprintf(
'User #%d "%s" has role "%s" in user group #%d "%s"', 'User #%d "%s" has role "%s" in user group #%d "%s"',
$this->id, $this->id,
$this->email, $this->email,
$membership->userRole->title, $membership->userRole->title,
$userGroup->id, $userGroup->id,
$userGroup->title $userGroup->title
)); ));
if (in_array($membership->userRole->title, $dbRolesTitles, true)) { if (in_array($membership->userRole->title, $dbRolesTitles, true)) {
app('log')->debug(sprintf('Return true, found role "%s"', $membership->userRole->title)); app('log')->debug(sprintf('Return true, found role "%s"', $membership->userRole->title));
@ -299,13 +297,13 @@ class User extends Authenticatable
} }
} }
app('log')->error(sprintf( app('log')->error(sprintf(
'User #%d "%s" does not have roles %s in user group #%d "%s"', 'User #%d "%s" does not have roles %s in user group #%d "%s"',
$this->id, $this->id,
$this->email, $this->email,
implode(', ', $roles), implode(', ', $roles),
$userGroup->id, $userGroup->id,
$userGroup->title $userGroup->title
)); ));
return false; return false;
} }
@ -357,13 +355,13 @@ class User extends Authenticatable
*/ */
public function routeNotificationFor($driver, $notification = null) public function routeNotificationFor($driver, $notification = null)
{ {
$method = 'routeNotificationFor' . Str::studly($driver); $method = 'routeNotificationFor'.Str::studly($driver);
if (method_exists($this, $method)) { if (method_exists($this, $method)) {
return $this->{$method}($notification); // @phpstan-ignore-line return $this->{$method}($notification); // @phpstan-ignore-line
} }
$email = $this->email; $email = $this->email;
// see if user has alternative email address: // see if user has alternative email address:
$pref = app('preferences')->getForUser($this, 'remote_guard_alt_email'); $pref = app('preferences')->getForUser($this, 'remote_guard_alt_email');
if (null !== $pref) { if (null !== $pref) {
$email = $pref->data; $email = $pref->data;
} }
@ -400,36 +398,36 @@ class User extends Authenticatable
$appToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data; $appToken = (string) app('fireflyconfig')->getEncrypted('pushover_app_token', '')->data;
$userToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data; $userToken = (string) app('fireflyconfig')->getEncrypted('pushover_user_token', '')->data;
if (property_exists($notification, 'type') && $notification->type === 'owner') { if (property_exists($notification, 'type') && 'owner' === $notification->type) {
return PushoverReceiver::withUserKey($userToken) return PushoverReceiver::withUserKey($userToken)
->withApplicationToken($appToken); ->withApplicationToken($appToken)
;
} }
throw new FireflyException('No pushover token found.'); throw new FireflyException('No pushover token found.');
// return PushoverReceiver::withUserKey((string) config('services.pushover.user_token')) // return PushoverReceiver::withUserKey((string) config('services.pushover.user_token'))
// ->withApplicationToken((string) config('services.pushover.token')); // ->withApplicationToken((string) config('services.pushover.token'));
//return (string) config('services.pushover.token'); // return (string) config('services.pushover.token');
} }
/** /**
* Route notifications for the Slack channel. * Route notifications for the Slack channel.
*/ */
public function routeNotificationForSlack(Notification $notification): ?string public function routeNotificationForSlack(Notification $notification): ?string
{ {
// this check does not validate if the user is owner, Should be done by notification itself. // this check does not validate if the user is owner, Should be done by notification itself.
$res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data; $res = app('fireflyconfig')->getEncrypted('slack_webhook_url', '')->data;
if (is_array($res)) { if (is_array($res)) {
$res = ''; $res = '';
} }
$res = (string) $res; $res = (string) $res;
if (property_exists($notification, 'type') && $notification->type === 'owner') { if (property_exists($notification, 'type') && 'owner' === $notification->type) {
return $res; return $res;
} }
// not the best way to do this, but alas. // not the best way to do this, but alas.
if ($notification instanceof UserRegistration) { if ($notification instanceof UserRegistration) {
return $res; return $res;
} }
if ($notification instanceof VersionCheckResult) { if ($notification instanceof VersionCheckResult) {

View File

@ -813,14 +813,15 @@ class FireflyValidator extends Validator
public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool public function validateUniquePiggyBankForUser($attribute, $value, $parameters): bool
{ {
$exclude = $parameters[0] ?? null; $exclude = $parameters[0] ?? null;
$query = PiggyBank $query = PiggyBank::leftJoin('account_piggy_bank', 'account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
::leftJoin('account_piggy_bank','account_piggy_bank.piggy_bank_id', '=', 'piggy_banks.id')
->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id') ->leftJoin('accounts', 'accounts.id', '=', 'account_piggy_bank.account_id')
->where('accounts.user_id', auth()->user()->id); ->where('accounts.user_id', auth()->user()->id)
;
if (null !== $exclude) { if (null !== $exclude) {
$query->where('piggy_banks.id', '!=', (int) $exclude); $query->where('piggy_banks.id', '!=', (int) $exclude);
} }
$query->where('piggy_banks.name', $value); $query->where('piggy_banks.name', $value);
return 0 === $query->get(['piggy_banks.*'])->count(); return 0 === $query->get(['piggy_banks.*'])->count();
} }

View File

@ -428,7 +428,7 @@ return [
'transfers' => 'fa-exchange', 'transfers' => 'fa-exchange',
], ],
'bindables' => [ 'bindables' => [
// models // models
'account' => Account::class, 'account' => Account::class,
'attachment' => Attachment::class, 'attachment' => Attachment::class,
@ -486,7 +486,7 @@ return [
'userGroupBill' => UserGroupBill::class, 'userGroupBill' => UserGroupBill::class,
'userGroup' => UserGroup::class, 'userGroup' => UserGroup::class,
], ],
'rule-actions' => [ 'rule-actions' => [
'set_category' => SetCategory::class, 'set_category' => SetCategory::class,
'clear_category' => ClearCategory::class, 'clear_category' => ClearCategory::class,
'set_budget' => SetBudget::class, 'set_budget' => SetBudget::class,
@ -520,7 +520,7 @@ return [
// 'set_foreign_amount' => SetForeignAmount::class, // 'set_foreign_amount' => SetForeignAmount::class,
// 'set_foreign_currency' => SetForeignCurrency::class, // 'set_foreign_currency' => SetForeignCurrency::class,
], ],
'context-rule-actions' => [ 'context-rule-actions' => [
'set_category', 'set_category',
'set_budget', 'set_budget',
'add_tag', 'add_tag',
@ -539,16 +539,16 @@ return [
'convert_transfer', 'convert_transfer',
], ],
'test-triggers' => [ 'test-triggers' => [
'limit' => 10, 'limit' => 10,
'range' => 200, 'range' => 200,
], ],
// expected source types for each transaction type, in order of preference. // expected source types for each transaction type, in order of preference.
'expected_source_types' => [ 'expected_source_types' => [
'source' => [ 'source' => [
TransactionTypeEnum::WITHDRAWAL->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::WITHDRAWAL->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::CASH->value], TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::CASH->value],
TransactionTypeEnum::TRANSFER->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::TRANSFER->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
TransactionTypeEnum::OPENING_BALANCE->value => [ TransactionTypeEnum::OPENING_BALANCE->value => [
AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::INITIAL_BALANCE->value,
@ -560,7 +560,7 @@ return [
TransactionTypeEnum::RECONCILIATION->value => [AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::ASSET->value], TransactionTypeEnum::RECONCILIATION->value => [AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::ASSET->value],
TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
// in case no transaction type is known yet, it could be anything. // in case no transaction type is known yet, it could be anything.
'none' => [ 'none' => [
AccountTypeEnum::ASSET->value, AccountTypeEnum::ASSET->value,
AccountTypeEnum::EXPENSE->value, AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::REVENUE->value, AccountTypeEnum::REVENUE->value,
@ -577,7 +577,7 @@ return [
AccountTypeEnum::EXPENSE->value, AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::CASH->value, AccountTypeEnum::CASH->value,
], ],
TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::DEPOSIT->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
TransactionTypeEnum::TRANSFER->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::TRANSFER->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
TransactionTypeEnum::OPENING_BALANCE->value => [ TransactionTypeEnum::OPENING_BALANCE->value => [
AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::INITIAL_BALANCE->value,
@ -590,7 +590,7 @@ return [
TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], TransactionTypeEnum::LIABILITY_CREDIT->value => [AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
], ],
], ],
'allowed_opposing_types' => [ 'allowed_opposing_types' => [
'source' => [ 'source' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value, AccountTypeEnum::ASSET->value,
@ -680,7 +680,7 @@ return [
], ],
], ],
// depending on the account type, return the allowed transaction types: // depending on the account type, return the allowed transaction types:
'allowed_transaction_types' => [ 'allowed_transaction_types' => [
'source' => [ 'source' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::WITHDRAWAL->value,
@ -749,7 +749,7 @@ return [
], ],
// having the source + dest will tell you the transaction type. // having the source + dest will tell you the transaction type.
'account_to_transaction' => [ 'account_to_transaction' => [
AccountTypeEnum::ASSET->value => [ AccountTypeEnum::ASSET->value => [
AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value, AccountTypeEnum::ASSET->value => TransactionTypeEnum::TRANSFER->value,
AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value, AccountTypeEnum::CASH->value => TransactionTypeEnum::WITHDRAWAL->value,
@ -814,14 +814,14 @@ return [
], ],
// allowed source -> destination accounts. // allowed source -> destination accounts.
'source_dests' => [ 'source_dests' => [
TransactionTypeEnum::WITHDRAWAL->value => [ TransactionTypeEnum::WITHDRAWAL->value => [
AccountTypeEnum::ASSET->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CASH->value], AccountTypeEnum::ASSET->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CASH->value],
AccountTypeEnum::LOAN->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value], AccountTypeEnum::LOAN->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value],
AccountTypeEnum::DEBT->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value], AccountTypeEnum::DEBT->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value],
AccountTypeEnum::MORTGAGE->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value], AccountTypeEnum::MORTGAGE->value => [AccountTypeEnum::EXPENSE->value, AccountTypeEnum::CASH->value],
], ],
TransactionTypeEnum::DEPOSIT->value => [ TransactionTypeEnum::DEPOSIT->value => [
AccountTypeEnum::REVENUE->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], AccountTypeEnum::REVENUE->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
AccountTypeEnum::CASH->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], AccountTypeEnum::CASH->value => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
AccountTypeEnum::LOAN->value => [AccountTypeEnum::ASSET->value], AccountTypeEnum::LOAN->value => [AccountTypeEnum::ASSET->value],
@ -853,7 +853,7 @@ return [
], ],
], ],
// if you add fields to this array, don't forget to update the export routine (ExportDataGenerator). // if you add fields to this array, don't forget to update the export routine (ExportDataGenerator).
'journal_meta_fields' => [ 'journal_meta_fields' => [
// sepa // sepa
'sepa_cc', 'sepa_cc',
'sepa_ct_op', 'sepa_ct_op',
@ -887,31 +887,31 @@ return [
'recurrence_count', 'recurrence_count',
'recurrence_date', 'recurrence_date',
], ],
'webhooks' => [ 'webhooks' => [
'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3), 'max_attempts' => env('WEBHOOK_MAX_ATTEMPTS', 3),
], ],
'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value], 'can_have_virtual_amounts' => [AccountTypeEnum::ASSET->value],
'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], 'can_have_opening_balance' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
'dynamic_creation_allowed' => [ 'dynamic_creation_allowed' => [
AccountTypeEnum::EXPENSE->value, AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::REVENUE->value, AccountTypeEnum::REVENUE->value,
AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::RECONCILIATION->value,
AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LIABILITY_CREDIT->value,
], ],
'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], 'valid_asset_fields' => ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'], 'valid_cc_fields' => ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth'],
'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'], 'valid_account_fields' => ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth', 'liability_direction'],
// dynamic date ranges are as follows: // dynamic date ranges are as follows:
'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'], 'dynamic_date_ranges' => ['last7', 'last30', 'last90', 'last365', 'MTD', 'QTD', 'YTD'],
// only used in v1 // only used in v1
'allowed_sort_parameters' => ['order', 'name', 'iban'], 'allowed_sort_parameters' => ['order', 'name', 'iban'],
// preselected account lists possibilities: // preselected account lists possibilities:
'preselected_accounts' => ['all', 'assets', 'liabilities'], 'preselected_accounts' => ['all', 'assets', 'liabilities'],
// allowed to store a piggy bank in: // allowed to store a piggy bank in:
'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value], 'piggy_bank_account_types' => [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
]; ];

View File

@ -1,4 +1,5 @@
<?php <?php
/* /*
* notifications.php * notifications.php
* Copyright (c) 2024 james@firefly-iii.org. * Copyright (c) 2024 james@firefly-iii.org.
@ -20,14 +21,15 @@
*/ */
declare(strict_types=1); declare(strict_types=1);
return [ return [
'channels' => [ 'channels' => [
'email' => ['enabled' => true, 'ui_configurable' => 0,], 'email' => ['enabled' => true, 'ui_configurable' => 0],
'slack' => ['enabled' => true, 'ui_configurable' => 1,], 'slack' => ['enabled' => true, 'ui_configurable' => 1],
'ntfy' => ['enabled' => true, 'ui_configurable' => 1,], 'ntfy' => ['enabled' => true, 'ui_configurable' => 1],
'pushover' => ['enabled' => true, 'ui_configurable' => 1,], 'pushover' => ['enabled' => true, 'ui_configurable' => 1],
'gotify' => ['enabled' => false, 'ui_configurable' => 0,], 'gotify' => ['enabled' => false, 'ui_configurable' => 0],
'pushbullet' => ['enabled' => false, 'ui_configurable' => 0,], 'pushbullet' => ['enabled' => false, 'ui_configurable' => 0],
], ],
'notifications' => [ 'notifications' => [
'user' => [ 'user' => [
@ -49,13 +51,13 @@ return [
'new_backup_codes' => ['enabled' => true, 'configurable' => false], 'new_backup_codes' => ['enabled' => true, 'configurable' => false],
], ],
'owner' => [ 'owner' => [
//'invitation_created' => ['enabled' => true], // 'invitation_created' => ['enabled' => true],
// 'some_notification' => ['enabled' => true], // 'some_notification' => ['enabled' => true],
'admin_new_reg' => ['enabled' => true], 'admin_new_reg' => ['enabled' => true],
'user_new_reg' => ['enabled' => true], 'user_new_reg' => ['enabled' => true],
'new_version' => ['enabled' => true], 'new_version' => ['enabled' => true],
'invite_created' => ['enabled' => true], 'invite_created' => ['enabled' => true],
'invite_redeemed' => ['enabled' => true], 'invite_redeemed' => ['enabled' => true],
'unknown_user_attempt' => ['enabled' => true], 'unknown_user_attempt' => ['enabled' => true],
], ],
], ],

View File

@ -1,12 +1,14 @@
<?php <?php
declare(strict_types=1);
// config for Wijourdil/NtfyNotificationChannel // config for Wijourdil/NtfyNotificationChannel
return [ return [
'server' => 'https://ntfy.sh', 'server' => 'https://ntfy.sh',
'topic' => '', 'topic' => '',
'authentication' => [ 'authentication' => [
'enabled' => false, 'enabled' => false,
'username' => '', 'username' => '',
'password' => '', 'password' => '',
], ],

View File

@ -61,8 +61,8 @@ return [
'mandrill' => [ 'mandrill' => [
'secret' => env('MANDRILL_SECRET'), 'secret' => env('MANDRILL_SECRET'),
], ],
'pushover' => [ 'pushover' => [
'token' => 'fake_token', 'token' => 'fake_token',
'user_token' => 'fake_token', 'user_token' => 'fake_token',
], ],
]; ];

View File

@ -1,31 +1,32 @@
<?php <?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint; use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema; use Illuminate\Support\Facades\Schema;
return new class extends Migration return new class () extends Migration {
{
/** /**
* Run the migrations. * Run the migrations.
*/ */
public function up(): void public function up(): void
{ {
// make account_id nullable and the relation also nullable. // make account_id nullable and the relation also nullable.
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 1. drop index // 1. drop index
$table->dropForeign('piggy_banks_account_id_foreign'); $table->dropForeign('piggy_banks_account_id_foreign');
}); });
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 2. make column nullable. // 2. make column nullable.
$table->unsignedInteger('account_id')->nullable()->change(); $table->unsignedInteger('account_id')->nullable()->change();
}); });
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 3. add currency // 3. add currency
$table->integer('transaction_currency_id', false, true)->after('account_id'); $table->integer('transaction_currency_id', false, true)->after('account_id');
$table->foreign('transaction_currency_id','unique_currency')->references('id')->on('transaction_currencies')->onDelete('cascade'); $table->foreign('transaction_currency_id', 'unique_currency')->references('id')->on('transaction_currencies')->onDelete('cascade');
}); });
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 4. rename columns // 4. rename columns
$table->renameColumn('targetamount', 'target_amount'); $table->renameColumn('targetamount', 'target_amount');
$table->renameColumn('startdate', 'start_date'); $table->renameColumn('startdate', 'start_date');
@ -33,13 +34,13 @@ return new class extends Migration
$table->renameColumn('startdate_tz', 'start_date_tz'); $table->renameColumn('startdate_tz', 'start_date_tz');
$table->renameColumn('targetdate_tz', 'target_date_tz'); $table->renameColumn('targetdate_tz', 'target_date_tz');
}); });
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 5. add new index // 5. add new index
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('set null'); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('set null');
}); });
// rename some fields in piggy bank reps. // rename some fields in piggy bank reps.
Schema::table('piggy_bank_repetitions', static function (Blueprint $table) { Schema::table('piggy_bank_repetitions', static function (Blueprint $table): void {
// 6. rename columns // 6. rename columns
$table->renameColumn('currentamount', 'current_amount'); $table->renameColumn('currentamount', 'current_amount');
$table->renameColumn('startdate', 'start_date'); $table->renameColumn('startdate', 'start_date');
@ -49,14 +50,14 @@ return new class extends Migration
}); });
// create table account_piggy_bank // create table account_piggy_bank
Schema::create('account_piggy_bank', static function (Blueprint $table) { Schema::create('account_piggy_bank', static function (Blueprint $table): void {
$table->id(); $table->id();
$table->integer('account_id', false, true); $table->integer('account_id', false, true);
$table->integer('piggy_bank_id',false, true); $table->integer('piggy_bank_id', false, true);
$table->decimal('current_amount', 32, 12)->default('0'); $table->decimal('current_amount', 32, 12)->default('0');
$table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade'); $table->foreign('account_id')->references('id')->on('accounts')->onDelete('cascade');
$table->foreign('piggy_bank_id')->references('id')->on('piggy_banks')->onDelete('cascade'); $table->foreign('piggy_bank_id')->references('id')->on('piggy_banks')->onDelete('cascade');
$table->unique(['account_id', 'piggy_bank_id'],'unique_piggy_save'); $table->unique(['account_id', 'piggy_bank_id'], 'unique_piggy_save');
}); });
} }
@ -66,7 +67,7 @@ return new class extends Migration
*/ */
public function down(): void public function down(): void
{ {
Schema::table('piggy_banks', static function (Blueprint $table) { Schema::table('piggy_banks', static function (Blueprint $table): void {
// 1. drop account index again. // 1. drop account index again.
$table->dropForeign('piggy_banks_account_id_foreign'); $table->dropForeign('piggy_banks_account_id_foreign');
@ -89,7 +90,7 @@ return new class extends Migration
}); });
// rename some fields in piggy bank reps. // rename some fields in piggy bank reps.
Schema::table('piggy_bank_repetitions', static function (Blueprint $table) { Schema::table('piggy_bank_repetitions', static function (Blueprint $table): void {
// 6. rename columns // 6. rename columns
$table->renameColumn('current_amount', 'currentamount'); $table->renameColumn('current_amount', 'currentamount');
$table->renameColumn('start_date', 'startdate'); $table->renameColumn('start_date', 'startdate');

View File

@ -88,5 +88,5 @@ return [
'mfa_disableMFA' => 'Disable multi-factor authentication', 'mfa_disableMFA' => 'Disable multi-factor authentication',
// notifications // notifications
'notification_index' => 'Owner notifications', 'notification_index' => 'Owner notifications',
]; ];

View File

@ -26,160 +26,160 @@ declare(strict_types=1);
return [ return [
// common items // common items
'greeting' => 'Hi there,', 'greeting' => 'Hi there,',
'closing' => 'Beep boop,', 'closing' => 'Beep boop,',
'signature' => 'The Firefly III Mail Robot', 'signature' => 'The Firefly III Mail Robot',
'footer_ps' => 'PS: This message was sent because a request from IP :ipAddress triggered it.', 'footer_ps' => 'PS: This message was sent because a request from IP :ipAddress triggered it.',
// admin test // admin test
'admin_test_subject' => 'A test message from your Firefly III installation', 'admin_test_subject' => 'A test message from your Firefly III installation',
'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.', 'admin_test_body' => 'This is a test message from your Firefly III instance. It was sent to :email.',
'admin_test_message' => 'This is a test message from your Firefly III instance over channel ":channel".', 'admin_test_message' => 'This is a test message from your Firefly III instance over channel ":channel".',
// Ignore this comment // Ignore this comment
// invite // invite
'invitation_created_subject' => 'An invitation has been created', 'invitation_created_subject' => 'An invitation has been created',
'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.', 'invitation_created_body' => 'Admin user ":email" created a user invitation which can be used by whoever is behind email address ":invitee". The invite will be valid for 48hrs.',
'invite_user_subject' => 'You\'ve been invited to create a Firefly III account.', 'invite_user_subject' => 'You\'ve been invited to create a Firefly III account.',
'invitation_introduction' => 'You\'ve been invited to create a Firefly III account on **:host**. Firefly III is a personal, self-hosted, private personal finance manager. All the cool kids are using it.', 'invitation_introduction' => 'You\'ve been invited to create a Firefly III account on **:host**. Firefly III is a personal, self-hosted, private personal finance manager. All the cool kids are using it.',
'invitation_invited_by' => 'You\'ve been invited by ":admin" and this invitation was sent to ":invitee". That\'s you, right?', 'invitation_invited_by' => 'You\'ve been invited by ":admin" and this invitation was sent to ":invitee". That\'s you, right?',
'invitation_url' => 'The invitation is valid for 48 hours and can be redeemed by surfing to [Firefly III](:url). Enjoy!', 'invitation_url' => 'The invitation is valid for 48 hours and can be redeemed by surfing to [Firefly III](:url). Enjoy!',
// new IP // new IP
'login_from_new_ip' => 'New login on Firefly III', 'login_from_new_ip' => 'New login on Firefly III',
'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)', 'slack_login_from_new_ip' => 'New Firefly III login from IP :ip (:host)',
'new_ip_body' => 'Firefly III detected a new login on your account from an unknown IP address. If you never logged in from the IP address below, or it has been more than six months ago, Firefly III will warn you.', 'new_ip_body' => 'Firefly III detected a new login on your account from an unknown IP address. If you never logged in from the IP address below, or it has been more than six months ago, Firefly III will warn you.',
'new_ip_warning' => 'If you recognize this IP address or the login, you can ignore this message. If you didn\'t login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!', 'new_ip_warning' => 'If you recognize this IP address or the login, you can ignore this message. If you didn\'t login, of if you have no idea what this is about, verify your password security, change it, and log out all other sessions. To do this, go to your profile page. Of course you have 2FA enabled already, right? Stay safe!',
'ip_address' => 'IP address', 'ip_address' => 'IP address',
'host_name' => 'Host', 'host_name' => 'Host',
'date_time' => 'Date + time', 'date_time' => 'Date + time',
// access token created // access token created
'access_token_created_subject' => 'A new access token was created', 'access_token_created_subject' => 'A new access token was created',
'access_token_created_body' => 'Somebody (hopefully you) just created a new Firefly III API Access Token for your user account.', 'access_token_created_body' => 'Somebody (hopefully you) just created a new Firefly III API Access Token for your user account.',
'access_token_created_explanation' => 'With this token, they can access **all** of your financial records through the Firefly III API.', 'access_token_created_explanation' => 'With this token, they can access **all** of your financial records through the Firefly III API.',
'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url', 'access_token_created_revoke' => 'If this wasn\'t you, please revoke this token as soon as possible at :url',
// unknown user login attempt // unknown user login attempt
'unknown_user_subject' => 'An unknown user tried to log in', 'unknown_user_subject' => 'An unknown user tried to log in',
'unknown_user_body' => 'An unknown user tried to log in to Firefly III. The email address they used was ":address".', 'unknown_user_body' => 'An unknown user tried to log in to Firefly III. The email address they used was ":address".',
'unknown_user_message' => 'The email address they used was ":address".', 'unknown_user_message' => 'The email address they used was ":address".',
// registered // registered
'registered_subject' => 'Welcome to Firefly III!', 'registered_subject' => 'Welcome to Firefly III!',
'registered_subject_admin' => 'A new user has registered', 'registered_subject_admin' => 'A new user has registered',
'admin_new_user_registered' => 'A new user has registered. User **:email** was given user ID #:id.', 'admin_new_user_registered' => 'A new user has registered. User **:email** was given user ID #:id.',
'registered_welcome' => 'Welcome to [Firefly III](:address). Your registration has made it, and this email is here to confirm it. Yay!', 'registered_welcome' => 'Welcome to [Firefly III](:address). Your registration has made it, and this email is here to confirm it. Yay!',
'registered_pw' => 'If you have forgotten your password already, please reset it using [the password reset tool](:address/password/reset).', 'registered_pw' => 'If you have forgotten your password already, please reset it using [the password reset tool](:address/password/reset).',
'registered_help' => 'There is a help-icon in the top right corner of each page. If you need help, click it!', 'registered_help' => 'There is a help-icon in the top right corner of each page. If you need help, click it!',
'registered_closing' => 'Enjoy!', 'registered_closing' => 'Enjoy!',
'registered_firefly_iii_link' => 'Firefly III:', 'registered_firefly_iii_link' => 'Firefly III:',
'registered_pw_reset_link' => 'Password reset:', 'registered_pw_reset_link' => 'Password reset:',
'registered_doc_link' => 'Documentation:', 'registered_doc_link' => 'Documentation:',
// Ignore this comment // Ignore this comment
// new version // new version
'new_version_email_subject' => 'A new Firefly III version is available', 'new_version_email_subject' => 'A new Firefly III version is available',
// email change // email change
'email_change_subject' => 'Your Firefly III email address has changed', 'email_change_subject' => 'Your Firefly III email address has changed',
'email_change_body_to_new' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this message, please ignore and delete it.', 'email_change_body_to_new' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this message, please ignore and delete it.',
'email_change_body_to_old' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this to happen, you **must** follow the "undo"-link below to protect your account!', 'email_change_body_to_old' => 'You or somebody with access to your Firefly III account has changed your email address. If you did not expect this to happen, you **must** follow the "undo"-link below to protect your account!',
'email_change_ignore' => 'If you initiated this change, you may safely ignore this message.', 'email_change_ignore' => 'If you initiated this change, you may safely ignore this message.',
'email_change_old' => 'The old email address was: :email', 'email_change_old' => 'The old email address was: :email',
'email_change_old_strong' => 'The old email address was: **:email**', 'email_change_old_strong' => 'The old email address was: **:email**',
'email_change_new' => 'The new email address is: :email', 'email_change_new' => 'The new email address is: :email',
'email_change_new_strong' => 'The new email address is: **:email**', 'email_change_new_strong' => 'The new email address is: **:email**',
'email_change_instructions' => 'You cannot use Firefly III until you confirm this change. Please follow the link below to do so.', 'email_change_instructions' => 'You cannot use Firefly III until you confirm this change. Please follow the link below to do so.',
'email_change_undo_link' => 'To undo the change, follow this link:', 'email_change_undo_link' => 'To undo the change, follow this link:',
// OAuth token created // OAuth token created
'oauth_created_subject' => 'A new OAuth client has been created', 'oauth_created_subject' => 'A new OAuth client has been created',
'oauth_created_body' => 'Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It\'s labeled ":name" and has callback URL `:url`.', 'oauth_created_body' => 'Somebody (hopefully you) just created a new Firefly III API OAuth Client for your user account. It\'s labeled ":name" and has callback URL `:url`.',
'oauth_created_explanation' => 'With this client, they can access **all** of your financial records through the Firefly III API.', 'oauth_created_explanation' => 'With this client, they can access **all** of your financial records through the Firefly III API.',
'oauth_created_undo' => 'If this wasn\'t you, please revoke this client as soon as possible at `:url`', 'oauth_created_undo' => 'If this wasn\'t you, please revoke this client as soon as possible at `:url`',
// reset password // reset password
'reset_pw_subject' => 'Your password reset request', 'reset_pw_subject' => 'Your password reset request',
'reset_pw_instructions' => 'Somebody tried to reset your password. If it was you, please follow the link below to do so.', 'reset_pw_instructions' => 'Somebody tried to reset your password. If it was you, please follow the link below to do so.',
'reset_pw_warning' => '**PLEASE** verify that the link actually goes to the Firefly III you expect it to go!', 'reset_pw_warning' => '**PLEASE** verify that the link actually goes to the Firefly III you expect it to go!',
// error // error
'error_subject' => 'Caught an error in Firefly III', 'error_subject' => 'Caught an error in Firefly III',
'error_intro' => 'Firefly III v:version ran into an error: <span style="font-family: monospace;">:errorMessage</span>.', 'error_intro' => 'Firefly III v:version ran into an error: <span style="font-family: monospace;">:errorMessage</span>.',
'error_type' => 'The error was of type ":class".', 'error_type' => 'The error was of type ":class".',
'error_timestamp' => 'The error occurred on/at: :time.', 'error_timestamp' => 'The error occurred on/at: :time.',
'error_location' => 'This error occurred in file "<span style="font-family: monospace;">:file</span>" on line :line with code :code.', 'error_location' => 'This error occurred in file "<span style="font-family: monospace;">:file</span>" on line :line with code :code.',
'error_user' => 'The error was encountered by user #:id, <a href="mailto::email">:email</a>.', 'error_user' => 'The error was encountered by user #:id, <a href="mailto::email">:email</a>.',
'error_no_user' => 'There was no user logged in for this error or no user was detected.', 'error_no_user' => 'There was no user logged in for this error or no user was detected.',
'error_ip' => 'The IP address related to this error is: :ip', 'error_ip' => 'The IP address related to this error is: :ip',
'error_url' => 'URL is: :url', 'error_url' => 'URL is: :url',
'error_user_agent' => 'User agent: :userAgent', 'error_user_agent' => 'User agent: :userAgent',
'error_stacktrace' => 'The full stacktrace is below. If you think this is a bug in Firefly III, you can forward this message to <a href="mailto:james@firefly-iii.org?subject=I%20found%20a%20bug!">james@firefly-iii.org</a>. This can help fix the bug you just encountered.', 'error_stacktrace' => 'The full stacktrace is below. If you think this is a bug in Firefly III, you can forward this message to <a href="mailto:james@firefly-iii.org?subject=I%20found%20a%20bug!">james@firefly-iii.org</a>. This can help fix the bug you just encountered.',
'error_github_html' => 'If you prefer, you can also open a new issue on <a href="https://github.com/firefly-iii/firefly-iii/issues">GitHub</a>.', 'error_github_html' => 'If you prefer, you can also open a new issue on <a href="https://github.com/firefly-iii/firefly-iii/issues">GitHub</a>.',
'error_github_text' => 'If you prefer, you can also open a new issue on https://github.com/firefly-iii/firefly-iii/issues.', 'error_github_text' => 'If you prefer, you can also open a new issue on https://github.com/firefly-iii/firefly-iii/issues.',
'error_stacktrace_below' => 'The full stacktrace is below:', 'error_stacktrace_below' => 'The full stacktrace is below:',
'error_headers' => 'The following headers may also be relevant:', 'error_headers' => 'The following headers may also be relevant:',
'error_post' => 'This was submitted by the user:', 'error_post' => 'This was submitted by the user:',
// Ignore this comment // Ignore this comment
// report new journals // report new journals
'new_journals_subject' => 'Firefly III has created a new transaction|Firefly III has created :count new transactions', 'new_journals_subject' => 'Firefly III has created a new transaction|Firefly III has created :count new transactions',
'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:', 'new_journals_header' => 'Firefly III has created a transaction for you. You can find it in your Firefly III installation:|Firefly III has created :count transactions for you. You can find them in your Firefly III installation:',
// bill warning // bill warning
'bill_warning_subject_end_date' => 'Your bill ":name" is due to end in :diff days', 'bill_warning_subject_end_date' => 'Your bill ":name" is due to end in :diff days',
'bill_warning_subject_now_end_date' => 'Your bill ":name" is due to end TODAY', 'bill_warning_subject_now_end_date' => 'Your bill ":name" is due to end TODAY',
'bill_warning_subject_extension_date' => 'Your bill ":name" is due to be extended or cancelled in :diff days', 'bill_warning_subject_extension_date' => 'Your bill ":name" is due to be extended or cancelled in :diff days',
'bill_warning_subject_now_extension_date' => 'Your bill ":name" is due to be extended or cancelled TODAY', 'bill_warning_subject_now_extension_date' => 'Your bill ":name" is due to be extended or cancelled TODAY',
'bill_warning_end_date' => 'Your bill **":name"** is due to end on :date. This moment will pass in about **:diff days**.', 'bill_warning_end_date' => 'Your bill **":name"** is due to end on :date. This moment will pass in about **:diff days**.',
'bill_warning_extension_date' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass in about **:diff days**.', 'bill_warning_extension_date' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass in about **:diff days**.',
'bill_warning_end_date_zero' => 'Your bill **":name"** is due to end on :date. This moment will pass **TODAY!**', 'bill_warning_end_date_zero' => 'Your bill **":name"** is due to end on :date. This moment will pass **TODAY!**',
'bill_warning_extension_date_zero' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass **TODAY!**', 'bill_warning_extension_date_zero' => 'Your bill **":name"** is due to be extended or cancelled on :date. This moment will pass **TODAY!**',
'bill_warning_please_action' => 'Please take the appropriate action.', 'bill_warning_please_action' => 'Please take the appropriate action.',
// user has enabled MFA // user has enabled MFA
'enabled_mfa_subject' => 'You have enabled multi-factor authentication', 'enabled_mfa_subject' => 'You have enabled multi-factor authentication',
'enabled_mfa_slack' => 'You (:email) have enabled multi-factor authentication. Is this not correct? Check your settings!', 'enabled_mfa_slack' => 'You (:email) have enabled multi-factor authentication. Is this not correct? Check your settings!',
'have_enabled_mfa' => 'You have enabled multi-factor authentication on your Firefly III account ":email". This means that you will need to use an authenticator app to log in from now on.', 'have_enabled_mfa' => 'You have enabled multi-factor authentication on your Firefly III account ":email". This means that you will need to use an authenticator app to log in from now on.',
'enabled_mfa_warning' => 'If you did not enable this, please contact your administrator immediately or check out the Firefly III documentation.', 'enabled_mfa_warning' => 'If you did not enable this, please contact your administrator immediately or check out the Firefly III documentation.',
'disabled_mfa_subject' => 'You have disabled multi-factor authentication!', 'disabled_mfa_subject' => 'You have disabled multi-factor authentication!',
'disabled_mfa_slack' => 'You (:email) have disabled multi-factor authentication. Is this not correct? Check your settings!', 'disabled_mfa_slack' => 'You (:email) have disabled multi-factor authentication. Is this not correct? Check your settings!',
'have_disabled_mfa' => 'You have disabled multi-factor authentication on your Firefly III account ":email".', 'have_disabled_mfa' => 'You have disabled multi-factor authentication on your Firefly III account ":email".',
'disabled_mfa_warning' => 'If you did not disable this, please contact your administrator immediately or check out the Firefly III documentation.', 'disabled_mfa_warning' => 'If you did not disable this, please contact your administrator immediately or check out the Firefly III documentation.',
'new_backup_codes_subject' => 'You have generated new back-up codes', 'new_backup_codes_subject' => 'You have generated new back-up codes',
'new_backup_codes_slack' => 'You (:email) have generated new back-up codes. These can be used to login to Firefly III. Is this not correct? Check your settings!', 'new_backup_codes_slack' => 'You (:email) have generated new back-up codes. These can be used to login to Firefly III. Is this not correct? Check your settings!',
'new_backup_codes_intro' => 'You (:email) have generated new back-up codes. These can be used to login to Firefly III if you lose access to your authenticator app.', 'new_backup_codes_intro' => 'You (:email) have generated new back-up codes. These can be used to login to Firefly III if you lose access to your authenticator app.',
'new_backup_codes_warning' => 'Please store these codes in a safe place. If you lose them, you will not be able to log in to Firefly III. If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.', 'new_backup_codes_warning' => 'Please store these codes in a safe place. If you lose them, you will not be able to log in to Firefly III. If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.',
'used_backup_code_subject' => 'You have used a back-up code to login', 'used_backup_code_subject' => 'You have used a back-up code to login',
'used_backup_code_slack' => 'You (:email) have used a back-up code to login', 'used_backup_code_slack' => 'You (:email) have used a back-up code to login',
'used_backup_code_intro' => 'You (:email) have used a back-up code to login to Firefly III. You now have one less back-up code to login with. Please remove it from your list.', 'used_backup_code_intro' => 'You (:email) have used a back-up code to login to Firefly III. You now have one less back-up code to login with. Please remove it from your list.',
'used_backup_code_warning' => 'If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.', 'used_backup_code_warning' => 'If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.',
// few left: // few left:
'mfa_few_backups_left_subject' => 'You have only :count backup code(s) left!', 'mfa_few_backups_left_subject' => 'You have only :count backup code(s) left!',
'mfa_few_backups_left_slack' => 'You (:email) have only :count backup code(s) left!', 'mfa_few_backups_left_slack' => 'You (:email) have only :count backup code(s) left!',
'few_backup_codes_intro' => 'You (:email) have used most of your backup codes, and now have only :count left. Please generate new ones as soon as possible.', 'few_backup_codes_intro' => 'You (:email) have used most of your backup codes, and now have only :count left. Please generate new ones as soon as possible.',
'few_backup_codes_warning' => 'Without backup codes, you cannot recover your MFA login if you lose access to your code generator.', 'few_backup_codes_warning' => 'Without backup codes, you cannot recover your MFA login if you lose access to your code generator.',
// NO left: // NO left:
'mfa_no_backups_left_subject' => 'You have NO backup codes left!', 'mfa_no_backups_left_subject' => 'You have NO backup codes left!',
'mfa_no_backups_left_slack' => 'You (:email) NO backup codes left!', 'mfa_no_backups_left_slack' => 'You (:email) NO backup codes left!',
'no_backup_codes_intro' => 'You (:email) have used ALL of your backup codes. Please generate new ones as soon as possible.', 'no_backup_codes_intro' => 'You (:email) have used ALL of your backup codes. Please generate new ones as soon as possible.',
'no_backup_codes_warning' => 'Without backup codes, you cannot recover your MFA login if you lose access to your code generator.', 'no_backup_codes_warning' => 'Without backup codes, you cannot recover your MFA login if you lose access to your code generator.',
// many failed MFA attempts // many failed MFA attempts
'mfa_many_failed_subject' => 'You have tried and failed to use multi-factor authentication :count times now!', 'mfa_many_failed_subject' => 'You have tried and failed to use multi-factor authentication :count times now!',
'mfa_many_failed_slack' => 'You (:email) have tried and failed to use multi-factor authentication :count times now. Is this not correct? Check your settings!', 'mfa_many_failed_slack' => 'You (:email) have tried and failed to use multi-factor authentication :count times now. Is this not correct? Check your settings!',
'mfa_many_failed_attempts_intro' => 'You (:email) have tried :count times to use a multi-factor authentication code, but these login attempts have failed. Are you sure you are using the right MFA code? Are you sure the time on the server is correct?', 'mfa_many_failed_attempts_intro' => 'You (:email) have tried :count times to use a multi-factor authentication code, but these login attempts have failed. Are you sure you are using the right MFA code? Are you sure the time on the server is correct?',
'mfa_many_failed_attempts_warning' => 'If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.', 'mfa_many_failed_attempts_warning' => 'If you did not do this, please contact your administrator immediately or check out the Firefly III documentation.',
]; ];
// Ignore this comment // Ignore this comment

File diff suppressed because it is too large Load Diff

View File

@ -26,250 +26,250 @@ declare(strict_types=1);
return [ return [
// new user: // new user:
'bank_name' => 'Bank name', 'bank_name' => 'Bank name',
'bank_balance' => 'Balance', 'bank_balance' => 'Balance',
'current_balance' => 'Current balance', 'current_balance' => 'Current balance',
'savings_balance' => 'Savings balance', 'savings_balance' => 'Savings balance',
'credit_card_limit' => 'Credit card limit', 'credit_card_limit' => 'Credit card limit',
'automatch' => 'Match automatically', 'automatch' => 'Match automatically',
'skip' => 'Skip', 'skip' => 'Skip',
'enabled' => 'Enabled', 'enabled' => 'Enabled',
'name' => 'Name', 'name' => 'Name',
'active' => 'Active', 'active' => 'Active',
'amount_min' => 'Minimum amount', 'amount_min' => 'Minimum amount',
'amount_max' => 'Maximum amount', 'amount_max' => 'Maximum amount',
'match' => 'Matches on', 'match' => 'Matches on',
'strict' => 'Strict mode', 'strict' => 'Strict mode',
'repeat_freq' => 'Repeats', 'repeat_freq' => 'Repeats',
'object_group' => 'Group', 'object_group' => 'Group',
'location' => 'Location', 'location' => 'Location',
'update_channel' => 'Update channel', 'update_channel' => 'Update channel',
'currency_id' => 'Currency', 'currency_id' => 'Currency',
'transaction_currency_id' => 'Currency', 'transaction_currency_id' => 'Currency',
'auto_budget_currency_id' => 'Currency', 'auto_budget_currency_id' => 'Currency',
'external_ip' => 'Your server\'s external IP', 'external_ip' => 'Your server\'s external IP',
'attachments' => 'Attachments', 'attachments' => 'Attachments',
'BIC' => 'BIC', 'BIC' => 'BIC',
'verify_password' => 'Verify password security', 'verify_password' => 'Verify password security',
'source_account' => 'Source account', 'source_account' => 'Source account',
'destination_account' => 'Destination account', 'destination_account' => 'Destination account',
'asset_destination_account' => 'Destination account', 'asset_destination_account' => 'Destination account',
'include_net_worth' => 'Include in net worth', 'include_net_worth' => 'Include in net worth',
'asset_source_account' => 'Source account', 'asset_source_account' => 'Source account',
'journal_description' => 'Description', 'journal_description' => 'Description',
'note' => 'Notes', 'note' => 'Notes',
'currency' => 'Currency', 'currency' => 'Currency',
'account_id' => 'Asset account', 'account_id' => 'Asset account',
'budget_id' => 'Budget', 'budget_id' => 'Budget',
'bill_id' => 'Bill', 'bill_id' => 'Bill',
'opening_balance' => 'Opening balance', 'opening_balance' => 'Opening balance',
'tagMode' => 'Tag mode', 'tagMode' => 'Tag mode',
'virtual_balance' => 'Virtual balance', 'virtual_balance' => 'Virtual balance',
// Ignore this comment // Ignore this comment
'targetamount' => 'Target amount', 'targetamount' => 'Target amount',
'target_amount' => 'Target amount', 'target_amount' => 'Target amount',
'account_role' => 'Account role', 'account_role' => 'Account role',
'opening_balance_date' => 'Opening balance date', 'opening_balance_date' => 'Opening balance date',
'cc_type' => 'Credit card payment plan', 'cc_type' => 'Credit card payment plan',
'cc_monthly_payment_date' => 'Credit card monthly payment date', 'cc_monthly_payment_date' => 'Credit card monthly payment date',
'piggy_bank_id' => 'Piggy bank', 'piggy_bank_id' => 'Piggy bank',
'returnHere' => 'Return here', 'returnHere' => 'Return here',
'returnHereExplanation' => 'After storing, return here to create another one.', 'returnHereExplanation' => 'After storing, return here to create another one.',
'returnHereUpdateExplanation' => 'After updating, return here.', 'returnHereUpdateExplanation' => 'After updating, return here.',
'description' => 'Description', 'description' => 'Description',
'expense_account' => 'Expense account', 'expense_account' => 'Expense account',
'revenue_account' => 'Revenue account', 'revenue_account' => 'Revenue account',
'decimal_places' => 'Decimal places', 'decimal_places' => 'Decimal places',
'destination_amount' => 'Amount (destination)', 'destination_amount' => 'Amount (destination)',
'new_email_address' => 'New email address', 'new_email_address' => 'New email address',
'verification' => 'Verification', 'verification' => 'Verification',
'api_key' => 'API key', 'api_key' => 'API key',
'remember_me' => 'Remember me', 'remember_me' => 'Remember me',
'liability_type_id' => 'Liability type', 'liability_type_id' => 'Liability type',
'liability_type' => 'Liability type', 'liability_type' => 'Liability type',
'interest' => 'Interest', 'interest' => 'Interest',
'interest_period' => 'Interest period', 'interest_period' => 'Interest period',
'extension_date' => 'Extension date', 'extension_date' => 'Extension date',
'type' => 'Type', 'type' => 'Type',
'convert_Withdrawal' => 'Convert withdrawal', 'convert_Withdrawal' => 'Convert withdrawal',
'convert_Deposit' => 'Convert deposit', 'convert_Deposit' => 'Convert deposit',
'convert_Transfer' => 'Convert transfer', 'convert_Transfer' => 'Convert transfer',
'amount' => 'Amount', 'amount' => 'Amount',
'foreign_amount' => 'Foreign amount', 'foreign_amount' => 'Foreign amount',
'date' => 'Date', 'date' => 'Date',
'interest_date' => 'Interest date', 'interest_date' => 'Interest date',
'book_date' => 'Book date', 'book_date' => 'Book date',
'process_date' => 'Processing date', 'process_date' => 'Processing date',
'category' => 'Category', 'category' => 'Category',
'tags' => 'Tags', 'tags' => 'Tags',
'deletePermanently' => 'Delete permanently', 'deletePermanently' => 'Delete permanently',
'cancel' => 'Cancel', 'cancel' => 'Cancel',
'targetdate' => 'Target date', 'targetdate' => 'Target date',
'target_date' => 'Target date', 'target_date' => 'Target date',
'startdate' => 'Start date', 'startdate' => 'Start date',
'start_date' => 'Start date', 'start_date' => 'Start date',
'tag' => 'Tag', 'tag' => 'Tag',
'under' => 'Under', 'under' => 'Under',
'symbol' => 'Symbol', 'symbol' => 'Symbol',
'code' => 'Code', 'code' => 'Code',
'iban' => 'IBAN', 'iban' => 'IBAN',
'account_number' => 'Account number', 'account_number' => 'Account number',
'creditCardNumber' => 'Credit card number', 'creditCardNumber' => 'Credit card number',
'has_headers' => 'Headers', 'has_headers' => 'Headers',
'date_format' => 'Date format', 'date_format' => 'Date format',
'attachments[]' => 'Attachments', 'attachments[]' => 'Attachments',
'title' => 'Title', 'title' => 'Title',
'notes' => 'Notes', 'notes' => 'Notes',
'filename' => 'File name', 'filename' => 'File name',
'mime' => 'Mime type', 'mime' => 'Mime type',
'size' => 'Size', 'size' => 'Size',
'trigger' => 'Trigger', 'trigger' => 'Trigger',
'stop_processing' => 'Stop processing', 'stop_processing' => 'Stop processing',
'end_date' => 'End date', 'end_date' => 'End date',
'enddate' => 'End date', 'enddate' => 'End date',
'move_rules_before_delete' => 'Rule group', 'move_rules_before_delete' => 'Rule group',
'start' => 'Start of range', 'start' => 'Start of range',
'end' => 'End of range', 'end' => 'End of range',
'delete_account' => 'Delete account ":name"', 'delete_account' => 'Delete account ":name"',
'delete_webhook' => 'Delete webhook ":title"', 'delete_webhook' => 'Delete webhook ":title"',
'delete_bill' => 'Delete bill ":name"', 'delete_bill' => 'Delete bill ":name"',
'delete_budget' => 'Delete budget ":name"', 'delete_budget' => 'Delete budget ":name"',
'delete_category' => 'Delete category ":name"', 'delete_category' => 'Delete category ":name"',
'delete_currency' => 'Delete currency ":name"', 'delete_currency' => 'Delete currency ":name"',
'delete_journal' => 'Delete transaction with description ":description"', 'delete_journal' => 'Delete transaction with description ":description"',
'delete_attachment' => 'Delete attachment ":name"', 'delete_attachment' => 'Delete attachment ":name"',
'delete_rule' => 'Delete rule ":title"', 'delete_rule' => 'Delete rule ":title"',
'delete_rule_group' => 'Delete rule group ":title"', 'delete_rule_group' => 'Delete rule group ":title"',
'delete_link_type' => 'Delete link type ":name"', 'delete_link_type' => 'Delete link type ":name"',
'delete_user' => 'Delete user ":email"', 'delete_user' => 'Delete user ":email"',
'delete_recurring' => 'Delete recurring transaction ":title"', 'delete_recurring' => 'Delete recurring transaction ":title"',
'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.',
'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?',
'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?',
'account_areYouSure_js' => 'Are you sure you want to delete the account named "{name}"?', 'account_areYouSure_js' => 'Are you sure you want to delete the account named "{name}"?',
'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?',
'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?',
'object_group_areYouSure' => 'Are you sure you want to delete the group titled ":title"?', 'object_group_areYouSure' => 'Are you sure you want to delete the group titled ":title"?',
'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?',
'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?',
'webhook_areYouSure' => 'Are you sure you want to delete the webhook named ":title"?', 'webhook_areYouSure' => 'Are you sure you want to delete the webhook named ":title"?',
'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?',
'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?',
'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?',
'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?', 'piggyBank_areYouSure' => 'Are you sure you want to delete the piggy bank named ":name"?',
'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?',
'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?',
// Ignore this comment // Ignore this comment
'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?',
'journal_link_areYouSure' => 'Are you sure you want to delete the link between <a href=":source_link">:source</a> and <a href=":destination_link">:destination</a>?', 'journal_link_areYouSure' => 'Are you sure you want to delete the link between <a href=":source_link">:source</a> and <a href=":destination_link">:destination</a>?',
'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?',
'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.',
'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.',
'delete_all_permanently' => 'Delete selected permanently', 'delete_all_permanently' => 'Delete selected permanently',
'update_all_journals' => 'Update these transactions', 'update_all_journals' => 'Update these transactions',
'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.',
'also_delete_transactions_js' => 'No transactions|The only transaction connected to this account will be deleted as well.|All {count} transactions connected to this account will be deleted as well.', 'also_delete_transactions_js' => 'No transactions|The only transaction connected to this account will be deleted as well.|All {count} transactions connected to this account will be deleted as well.',
'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.',
'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.',
'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.',
'also_delete_piggyBanks_js' => 'No piggy banks|The only piggy bank connected to this account will be deleted as well.|All {count} piggy banks connected to this account will be deleted as well.', 'also_delete_piggyBanks_js' => 'No piggy banks|The only piggy bank connected to this account will be deleted as well.|All {count} piggy banks connected to this account will be deleted as well.',
'not_delete_piggy_banks' => 'The piggy bank connected to this group will not be deleted.|The :count piggy banks connected to this group will not be deleted.', 'not_delete_piggy_banks' => 'The piggy bank connected to this group will not be deleted.|The :count piggy banks connected to this group will not be deleted.',
'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.',
'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.',
'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.',
'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.',
'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.',
'check_for_updates' => 'Check for updates', 'check_for_updates' => 'Check for updates',
'liability_direction' => 'Liability in/out', 'liability_direction' => 'Liability in/out',
'delete_object_group' => 'Delete group ":title"', 'delete_object_group' => 'Delete group ":title"',
'email' => 'Email address', 'email' => 'Email address',
'password' => 'Password', 'password' => 'Password',
'password_confirmation' => 'Password (again)', 'password_confirmation' => 'Password (again)',
'blocked' => 'Is blocked?', 'blocked' => 'Is blocked?',
'blocked_code' => 'Reason for block', 'blocked_code' => 'Reason for block',
'login_name' => 'Login', 'login_name' => 'Login',
'is_owner' => 'Is admin?', 'is_owner' => 'Is admin?',
'url' => 'URL', 'url' => 'URL',
'bill_end_date' => 'End date', 'bill_end_date' => 'End date',
// import // import
'apply_rules' => 'Apply rules', 'apply_rules' => 'Apply rules',
'artist' => 'Artist', 'artist' => 'Artist',
'album' => 'Album', 'album' => 'Album',
'song' => 'Song', 'song' => 'Song',
// admin // admin
'domain' => 'Domain', 'domain' => 'Domain',
'single_user_mode' => 'Disable user registration', 'single_user_mode' => 'Disable user registration',
'is_demo_site' => 'Is demo site', 'is_demo_site' => 'Is demo site',
// import // import
'configuration_file' => 'Configuration file', 'configuration_file' => 'Configuration file',
'csv_comma' => 'A comma (,)', 'csv_comma' => 'A comma (,)',
'csv_semicolon' => 'A semicolon (;)', 'csv_semicolon' => 'A semicolon (;)',
'csv_tab' => 'A tab (invisible)', 'csv_tab' => 'A tab (invisible)',
'csv_delimiter' => 'CSV field delimiter', 'csv_delimiter' => 'CSV field delimiter',
'client_id' => 'Client ID', 'client_id' => 'Client ID',
'app_id' => 'App ID', 'app_id' => 'App ID',
'secret' => 'Secret', 'secret' => 'Secret',
'public_key' => 'Public key', 'public_key' => 'Public key',
'country_code' => 'Country code', 'country_code' => 'Country code',
'provider_code' => 'Bank or data-provider', 'provider_code' => 'Bank or data-provider',
'fints_url' => 'FinTS API URL', 'fints_url' => 'FinTS API URL',
'fints_port' => 'Port', 'fints_port' => 'Port',
'fints_bank_code' => 'Bank code', 'fints_bank_code' => 'Bank code',
'fints_username' => 'Username', 'fints_username' => 'Username',
'fints_password' => 'PIN / Password', 'fints_password' => 'PIN / Password',
'fints_account' => 'FinTS account', 'fints_account' => 'FinTS account',
'local_account' => 'Firefly III account', 'local_account' => 'Firefly III account',
// Ignore this comment // Ignore this comment
'from_date' => 'Date from', 'from_date' => 'Date from',
'to_date' => 'Date to', 'to_date' => 'Date to',
'due_date' => 'Due date', 'due_date' => 'Due date',
'payment_date' => 'Payment date', 'payment_date' => 'Payment date',
'invoice_date' => 'Invoice date', 'invoice_date' => 'Invoice date',
'internal_reference' => 'Internal reference', 'internal_reference' => 'Internal reference',
'inward' => 'Inward description', 'inward' => 'Inward description',
'outward' => 'Outward description', 'outward' => 'Outward description',
'rule_group_id' => 'Rule group', 'rule_group_id' => 'Rule group',
'transaction_description' => 'Transaction description', 'transaction_description' => 'Transaction description',
'first_date' => 'First date', 'first_date' => 'First date',
'transaction_type' => 'Transaction type', 'transaction_type' => 'Transaction type',
'repeat_until' => 'Repeat until', 'repeat_until' => 'Repeat until',
'recurring_description' => 'Recurring transaction description', 'recurring_description' => 'Recurring transaction description',
'repetition_type' => 'Type of repetition', 'repetition_type' => 'Type of repetition',
'foreign_currency_id' => 'Foreign currency', 'foreign_currency_id' => 'Foreign currency',
'repetition_end' => 'Repetition ends', 'repetition_end' => 'Repetition ends',
'repetitions' => 'Repetitions', 'repetitions' => 'Repetitions',
'calendar' => 'Calendar', 'calendar' => 'Calendar',
'weekend' => 'Weekend', 'weekend' => 'Weekend',
'client_secret' => 'Client secret', 'client_secret' => 'Client secret',
'withdrawal_destination_id' => 'Destination account', 'withdrawal_destination_id' => 'Destination account',
'deposit_source_id' => 'Source account', 'deposit_source_id' => 'Source account',
'expected_on' => 'Expected on', 'expected_on' => 'Expected on',
'paid' => 'Paid', 'paid' => 'Paid',
'auto_budget_type' => 'Auto-budget', 'auto_budget_type' => 'Auto-budget',
'auto_budget_amount' => 'Auto-budget amount', 'auto_budget_amount' => 'Auto-budget amount',
'auto_budget_period' => 'Auto-budget period', 'auto_budget_period' => 'Auto-budget period',
'collected' => 'Collected', 'collected' => 'Collected',
'submitted' => 'Submitted', 'submitted' => 'Submitted',
'key' => 'Key', 'key' => 'Key',
'value' => 'Content of record', 'value' => 'Content of record',
'webhook_delivery' => 'Delivery', 'webhook_delivery' => 'Delivery',
'webhook_response' => 'Response', 'webhook_response' => 'Response',
'webhook_trigger' => 'Trigger', 'webhook_trigger' => 'Trigger',
'pushover_app_token' => 'Pushover app token', 'pushover_app_token' => 'Pushover app token',
'pushover_user_token' => 'Pushover user token', 'pushover_user_token' => 'Pushover user token',
'ntfy_server' => 'Ntfy server', 'ntfy_server' => 'Ntfy server',
'ntfy_topic' => 'Ntfy topic', 'ntfy_topic' => 'Ntfy topic',
'ntfy_auth' => 'Ntfy authentication enabled', 'ntfy_auth' => 'Ntfy authentication enabled',
'ntfy_user' => 'Ntfy username', 'ntfy_user' => 'Ntfy username',
'ntfy_pass' => 'Ntfy password', 'ntfy_pass' => 'Ntfy password',
]; ];
// Ignore this comment // Ignore this comment

View File

@ -25,9 +25,9 @@
declare(strict_types=1); declare(strict_types=1);
return [ return [
'invalid_account_type' => 'A piggy bank can only be linked to asset accounts and liabilities', 'invalid_account_type' => 'A piggy bank can only be linked to asset accounts and liabilities',
'invalid_account_currency' => 'This account does not use the currency you have selected', 'invalid_account_currency' => 'This account does not use the currency you have selected',
'current_amount_too_much' => 'The combined amount in "current_amount" cannot exceed the "target_amount".', 'current_amount_too_much' => 'The combined amount in "current_amount" cannot exceed the "target_amount".',
'filter_must_be_in' => 'Filter ":filter" must be one of: :values', 'filter_must_be_in' => 'Filter ":filter" must be one of: :values',
'filter_not_string' => 'Filter ":filter" is expected to be a string of text', 'filter_not_string' => 'Filter ":filter" is expected to be a string of text',
'bad_api_filter' => 'This API endpoint does not support ":filter" as a filter.', 'bad_api_filter' => 'This API endpoint does not support ":filter" as a filter.',