Compare commits

..

30 Commits

Author SHA1 Message Date
github-actions[bot]
7c1b550c2c Merge pull request #11518 from firefly-iii/release-1768366825
🤖 Automatically merge the PR into the develop branch.
2026-01-14 06:00:33 +01:00
JC5
94a25d3137 🤖 Auto commit for release 'develop' on 2026-01-14 2026-01-14 06:00:25 +01:00
github-actions[bot]
be87a24b8f Merge pull request #11517 from firefly-iii/release-1768334732
🤖 Automatically merge the PR into the develop branch.
2026-01-13 21:05:40 +01:00
JC5
f6032d5502 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 21:05:32 +01:00
James Cole
7dcfb68fca Fix parse issue. 2026-01-13 20:59:54 +01:00
James Cole
419c796eac Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-13 20:54:44 +01:00
James Cole
1d5733adba Experimental command 2026-01-13 20:54:31 +01:00
github-actions[bot]
c589360ad9 Merge pull request #11516 from firefly-iii/release-1768333294
🤖 Automatically merge the PR into the develop branch.
2026-01-13 20:41:40 +01:00
JC5
d4687fb34f 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 20:41:34 +01:00
github-actions[bot]
9c4159ca3d Merge pull request #11515 from firefly-iii/release-1768333066
🤖 Automatically merge the PR into the develop branch.
2026-01-13 20:37:53 +01:00
JC5
93507c8b96 🤖 Auto commit for release 'develop' on 2026-01-13 2026-01-13 20:37:46 +01:00
James Cole
23d49a4194 Code clean up and restoration using Mago. 2026-01-13 20:37:41 +01:00
James Cole
858c44fbce Reset fix. 2026-01-13 20:32:42 +01:00
James Cole
95a6543a94 Fix config 2026-01-13 05:26:12 +01:00
James Cole
855d40cf2f Update lock file 2026-01-13 05:25:11 +01:00
James Cole
7d0fec6326 Mago config 2026-01-13 05:25:01 +01:00
James Cole
ebd7dca6a9 Do some code cleanup courtesy of Mago. 2026-01-13 05:14:58 +01:00
James Cole
1d41fc9845 Do some code cleanup courtesy of Mago. 2026-01-13 05:13:01 +01:00
github-actions[bot]
fe9ae9c810 Merge pull request #11505 from firefly-iii/release-1768204215
🤖 Automatically merge the PR into the develop branch.
2026-01-12 08:50:22 +01:00
JC5
84600c5208 🤖 Auto commit for release 'develop' on 2026-01-12 2026-01-12 08:50:15 +01:00
James Cole
f7e89cab0a Fix #11502 2026-01-11 17:18:31 +01:00
James Cole
8195630a6e Fix #11501 2026-01-11 10:12:48 +01:00
github-actions[bot]
d5bf80a0cb Merge pull request #11498 from firefly-iii/release-1768068342
🤖 Automatically merge the PR into the develop branch.
2026-01-10 19:05:49 +01:00
JC5
ef1c64096d 🤖 Auto commit for release 'develop' on 2026-01-10 2026-01-10 19:05:42 +01:00
James Cole
ef35eaffb4 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop 2026-01-10 18:28:21 +01:00
James Cole
869ee7c735 Add info 2026-01-10 18:28:10 +01:00
James Cole
6c534f01eb Catch null pointer. 2026-01-10 18:26:58 +01:00
github-actions[bot]
3a9d89b53d Merge pull request #11497 from firefly-iii/release-1768064652
🤖 Automatically merge the PR into the develop branch.
2026-01-10 18:04:21 +01:00
JC5
badff64cfd 🤖 Auto commit for release 'develop' on 2026-01-10 2026-01-10 18:04:12 +01:00
James Cole
abacfa212e Throw a 410. Don't report it. 2026-01-10 18:00:18 +01:00
73 changed files with 1012 additions and 878 deletions

View File

@@ -40,7 +40,8 @@ return $config->setRules(
[
// rule sets
'@PHP83Migration' => true,
'@PHP8x3Migration' => true,
'@PHP8x4Migration' => true,
'@PhpCsFixer' => true,
'@PhpCsFixer:risky' => true,
'@PSR12' => true,

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\AccountTypeEnum;
@@ -37,6 +36,7 @@ use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -51,7 +51,7 @@ class AccountController extends Controller
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
/** @var array<int, string> */
private array $balanceTypes;
private array $balanceTypes;
private AccountRepositoryInterface $repository;
/**
@@ -60,16 +60,14 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
$this->balanceTypes = [AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value];
}
@@ -83,24 +81,18 @@ class AccountController extends Controller
public function accounts(AutocompleteApiRequest $request): JsonResponse
{
Log::debug('Before All.');
[
'types' => $types,
'query' => $query,
'date' => $date,
'limit' => $limit,
]
= $request->attributes->all();
['types' => $types, 'query' => $query, 'date' => $date, 'limit' => $limit] = $request->attributes->all();
$date ??= today(config('app.timezone'));
// set date to end-of-day for account balance. so it is at $date 23:59:59
$date->endOfDay();
$return = [];
$timer = Timer::getInstance();
$return = [];
$timer = Timer::getInstance();
$timer->start(sprintf('AC accounts "%s"', $query));
$result = $this->repository->searchAccount((string)$query, $types, $limit);
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
$result = $this->repository->searchAccount((string) $query, $types, $limit);
$allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary);
/** @var Account $account */
foreach ($result as $account) {
@@ -118,17 +110,17 @@ class AccountController extends Controller
}
$return[] = [
'id' => (string)$account->id,
'id' => (string) $account->id,
'name' => $account->name,
'name_with_balance' => $nameWithBalance,
'active' => $account->active,
'type' => $account->accountType->type,
'currency_id' => (string)$useCurrency->id,
'currency_id' => (string) $useCurrency->id,
'currency_name' => $useCurrency->name,
'currency_code' => $useCurrency->code,
'currency_symbol' => $useCurrency->symbol,
'currency_decimal_places' => $useCurrency->decimal_places,
'account_currency_id' => (string)$currency->id,
'account_currency_id' => (string) $currency->id,
'account_currency_name' => $currency->name,
'account_currency_code' => $currency->code,
'account_currency_symbol' => $currency->symbol,
@@ -137,16 +129,13 @@ class AccountController extends Controller
}
// custom order.
usort(
$return,
static function (array $left, array $right): int {
$order = [AccountTypeEnum::ASSET->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::EXPENSE->value];
$posA = (int)array_search($left['type'], $order, true);
$posB = (int)array_search($right['type'], $order, true);
usort($return, static function (array $left, array $right): int {
$order = [AccountTypeEnum::ASSET->value, AccountTypeEnum::REVENUE->value, AccountTypeEnum::EXPENSE->value];
$posA = (int) array_search($left['type'], $order, true);
$posB = (int) array_search($right['type'], $order, true);
return $posA - $posB;
}
);
return $posA - $posB;
});
$timer->stop(sprintf('AC accounts "%s"', $query));
return response()->api($return);

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BillController
@@ -46,16 +46,14 @@ class BillController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,13 +63,7 @@ class BillController extends Controller
public function bills(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchBill($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Bill $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
'active' => $item->active,
]
);
$filtered = $result->map(static fn (Bill $item): array => ['id' => (string) $item->id, 'name' => $item->name, 'active' => $item->active]);
return response()->api($filtered->toArray());
}

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Budget;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BudgetController
@@ -46,16 +46,14 @@ class BudgetController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,13 +63,7 @@ class BudgetController extends Controller
public function budgets(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchBudget($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Budget $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
'active' => $item->active,
]
);
$filtered = $result->map(static fn (Budget $item): array => ['id' => (string) $item->id, 'name' => $item->name, 'active' => $item->active]);
return response()->api($filtered->toArray());
}

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Category;
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class CategoryController
@@ -46,16 +46,14 @@ class CategoryController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CategoryRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CategoryRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -65,12 +63,7 @@ class CategoryController extends Controller
public function categories(AutocompleteApiRequest $request): JsonResponse
{
$result = $this->repository->searchCategory($request->attributes->get('query'), $request->attributes->get('limit'));
$filtered = $result->map(
static fn (Category $item): array => [
'id' => (string) $item->id,
'name' => $item->name,
]
);
$filtered = $result->map(static fn (Category $item): array => ['id' => (string) $item->id, 'name' => $item->name]);
return response()->api($filtered->toArray());
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use Deprecated;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
@@ -33,6 +32,7 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class CurrencyController
@@ -48,16 +48,14 @@ class CurrencyController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CurrencyRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(CurrencyRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\ObjectGroup;
use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class ObjectGroupController
@@ -46,16 +46,14 @@ class ObjectGroupController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(ObjectGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(ObjectGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -69,11 +67,7 @@ class ObjectGroupController extends Controller
/** @var ObjectGroup $objectGroup */
foreach ($result as $objectGroup) {
$return[] = [
'id' => (string) $objectGroup->id,
'name' => $objectGroup->title,
'title' => $objectGroup->title,
];
$return[] = ['id' => (string) $objectGroup->id, 'name' => $objectGroup->title, 'title' => $objectGroup->title];
}
return response()->api($return);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
@@ -34,13 +33,14 @@ use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class PiggyBankController
*/
class PiggyBankController extends Controller
{
private AccountRepositoryInterface $accountRepository;
private AccountRepositoryInterface $accountRepository;
private PiggyBankRepositoryInterface $piggyRepository;
protected array $acceptedRoles = [UserRoleEnum::READ_PIGGY_BANKS];
@@ -50,19 +50,17 @@ class PiggyBankController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyRepository->setUser($this->user);
$this->piggyRepository->setUserGroup($this->userGroup);
$this->accountRepository->setUser($this->user);
$this->accountRepository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->piggyRepository = app(PiggyBankRepositoryInterface::class);
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->piggyRepository->setUser($this->user);
$this->piggyRepository->setUserGroup($this->userGroup);
$this->accountRepository->setUser($this->user);
$this->accountRepository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function piggyBanks(AutocompleteApiRequest $request): JsonResponse
@@ -108,7 +106,7 @@ class PiggyBankController extends Controller
'%s (%s / %s)',
$piggy->name,
Amount::formatAnything($currency, $currentAmount, false),
Amount::formatAnything($currency, $piggy->target_amount, false),
Amount::formatAnything($currency, $piggy->target_amount, false)
),
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Recurrence;
use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RecurrenceController
@@ -46,16 +46,14 @@ class RecurrenceController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RecurringRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RecurringRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function recurring(AutocompleteApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Rule;
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RuleController
@@ -38,7 +38,7 @@ use Illuminate\Http\JsonResponse;
class RuleController extends Controller
{
private RuleRepositoryInterface $repository;
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
protected array $acceptedRoles = [UserRoleEnum::READ_RULES];
/**
* RuleController constructor.
@@ -46,16 +46,14 @@ class RuleController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function rules(AutocompleteApiRequest $request): JsonResponse
@@ -66,7 +64,7 @@ class RuleController extends Controller
/** @var Rule $rule */
foreach ($rules as $rule) {
$response[] = [
'id' => (string)$rule->id,
'id' => (string) $rule->id,
'name' => $rule->title,
'description' => $rule->description,
'active' => $rule->active,

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\RuleGroup;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class RuleGroupController
@@ -46,16 +46,14 @@ class RuleGroupController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(RuleGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function ruleGroups(AutocompleteApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Tag;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class TagController
@@ -46,16 +46,14 @@ class TagController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TagRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TagRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function tags(AutocompleteApiRequest $request): JsonResponse
@@ -65,11 +63,7 @@ class TagController extends Controller
/** @var Tag $tag */
foreach ($result as $tag) {
$array[] = [
'id' => (string) $tag->id,
'name' => $tag->tag,
'tag' => $tag->tag,
];
$array[] = ['id' => (string) $tag->id, 'name' => $tag->tag, 'tag' => $tag->tag];
}
return response()->api($array);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteTransactionApiRequest;
@@ -34,6 +33,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
/**
@@ -43,7 +43,7 @@ class TransactionController extends Controller
{
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private TransactionGroupRepositoryInterface $groupRepository;
private JournalRepositoryInterface $repository;
private JournalRepositoryInterface $repository;
/**
* TransactionController constructor.
@@ -51,19 +51,17 @@ class TransactionController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(JournalRepositoryInterface::class);
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->groupRepository->setUser($this->user);
$this->groupRepository->setUserGroup($this->userGroup);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(JournalRepositoryInterface::class);
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
$this->repository->setUser($this->user);
$this->repository->setUserGroup($this->userGroup);
$this->groupRepository->setUser($this->user);
$this->groupRepository->setUserGroup($this->userGroup);
return $next($request);
}
);
return $next($request);
});
}
public function transactions(AutocompleteTransactionApiRequest $request): JsonResponse

View File

@@ -24,13 +24,13 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Autocomplete;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class TransactionTypeController
@@ -46,14 +46,12 @@ class TransactionTypeController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TransactionTypeRepositoryInterface::class);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(TransactionTypeRepositoryInterface::class);
return $next($request);
}
);
return $next($request);
});
}
public function transactionTypes(AutocompleteApiRequest $request): JsonResponse
@@ -64,11 +62,7 @@ class TransactionTypeController extends Controller
/** @var TransactionType $type */
foreach ($types as $type) {
// different key for consistency.
$array[] = [
'id' => (string) $type->id,
'name' => $type->type,
'type' => $type->type,
];
$array[] = ['id' => (string) $type->id, 'name' => $type->type, 'type' => $type->type];
}
return response()->api($array);

View File

@@ -49,9 +49,9 @@ class AccountController extends Controller
use CleansChartData;
use CollectsAccountsFromFilter;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private array $chartData = [];
private AccountRepositoryInterface $repository;
/**
@@ -60,16 +60,14 @@ class AccountController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->validateUserGroup($request);
$this->repository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
$this->validateUserGroup($request);
$this->repository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -115,14 +113,14 @@ class AccountController extends Controller
'label' => $account->name,
// the currency that belongs to the account.
'currency_id' => (string)$currency->id,
'currency_id' => (string) $currency->id,
'currency_name' => $currency->name,
'currency_code' => $currency->code,
'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places,
// the primary currency
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
// the default currency of the user (could be the same!)
'date' => $params['start']->toAtomString(),
@@ -136,7 +134,7 @@ class AccountController extends Controller
];
if ($this->convertToPrimary) {
$currentSet['pc_entries'] = [];
$currentSet['primary_currency_id'] = (string)$this->primaryCurrency->id;
$currentSet['primary_currency_id'] = (string) $this->primaryCurrency->id;
$currentSet['primary_currency_code'] = $this->primaryCurrency->code;
$currentSet['primary_currency_symbol'] = $this->primaryCurrency->symbol;
$currentSet['primary_currency_decimal_places'] = $this->primaryCurrency->decimal_places;
@@ -151,7 +149,6 @@ class AccountController extends Controller
$previous = $balance;
$currentSet['entries'][$label] = $balance;
// do the same for the primary currency balance, if relevant:
$pcBalance = null;
if ($this->convertToPrimary) {
@@ -160,6 +157,7 @@ class AccountController extends Controller
$currentSet['pc_entries'][$label] = $pcBalance;
}
$currentStart = Navigation::addPeriod($currentStart, $period);
// $currentStart->addDay();
}
$this->chartData[] = $currentSet;

View File

@@ -1,6 +1,5 @@
<?php
/*
* BalanceController.php
* Copyright (c) 2025 james@firefly-iii.org
@@ -25,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Chart\ChartRequest;
use FireflyIII\Enums\TransactionTypeEnum;
@@ -37,6 +35,7 @@ use FireflyIII\Support\Http\Api\AccountBalanceGrouped;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\CollectsAccountsFromFilter;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class BalanceController
@@ -45,10 +44,11 @@ class BalanceController extends Controller
{
use CleansChartData;
use CollectsAccountsFromFilter;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private GroupCollectorInterface $collector;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private array $chartData = [];
private GroupCollectorInterface $collector;
private AccountRepositoryInterface $repository;
// private TransactionCurrency $default;
@@ -56,19 +56,17 @@ class BalanceController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->collector->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->collector->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(AccountRepositoryInterface::class);
$this->collector = app(GroupCollectorInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->collector->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->collector->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -89,10 +87,16 @@ class BalanceController extends Controller
// get journals for entire period:
$this->collector->setRange($queryParameters['start'], $queryParameters['end'])
$this->collector
->setRange($queryParameters['start'], $queryParameters['end'])
->withAccountInformation()
->setXorAccounts($accounts)
->setTypes([TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::RECONCILIATION->value, TransactionTypeEnum::TRANSFER->value])
->setTypes([
TransactionTypeEnum::WITHDRAWAL->value,
TransactionTypeEnum::DEPOSIT->value,
TransactionTypeEnum::RECONCILIATION->value,
TransactionTypeEnum::TRANSFER->value,
])
;
$journals = $this->collector->getExtractedJournals();

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRangeRequest;
@@ -36,13 +35,14 @@ use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\Steam;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use FireflyIII\Support\Facades\Steam;
/**
* Class BudgetController
@@ -52,32 +52,30 @@ class BudgetController extends Controller
use CleansChartData;
use ValidatesUserGroupTrait;
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
protected OperationsRepositoryInterface $opsRepository;
private BudgetLimitRepositoryInterface $blRepository;
private array $currencies = [];
private BudgetRepositoryInterface $repository;
private BudgetLimitRepositoryInterface $blRepository;
private array $currencies = [];
private BudgetRepositoryInterface $repository;
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->opsRepository->setUserGroup($this->userGroup);
$this->blRepository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->opsRepository->setUser($this->user);
$this->blRepository->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->repository = app(BudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->repository->setUserGroup($this->userGroup);
$this->opsRepository->setUserGroup($this->userGroup);
$this->blRepository->setUserGroup($this->userGroup);
$this->repository->setUser($this->user);
$this->opsRepository->setUser($this->user);
$this->blRepository->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -158,18 +156,17 @@ class BudgetController extends Controller
$rows[] = $row;
}
// is always an array
$return = [];
foreach ($rows as $row) {
$current = [
'label' => $budget->name,
'currency_id' => (string)$row['currency_id'],
'currency_id' => (string) $row['currency_id'],
'currency_name' => $row['currency_name'],
'currency_code' => $row['currency_code'],
'currency_decimal_places' => $row['currency_decimal_places'],
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
@@ -181,12 +178,7 @@ class BudgetController extends Controller
'end_date' => $row['end'],
'yAxisID' => 0,
'type' => 'bar',
'entries' => [
'budgeted' => $row['budgeted'],
'spent' => $row['spent'],
'left' => $row['left'],
'overspent' => $row['overspent'],
],
'entries' => ['budgeted' => $row['budgeted'], 'spent' => $row['spent'], 'left' => $row['left'], 'overspent' => $row['overspent']],
'pc_entries' => [
'budgeted' => $row['pc_budgeted'],
'spent' => $row['pc_spent'],
@@ -233,11 +225,11 @@ class BudgetController extends Controller
foreach ($spent as $currencyId => $block) {
$this->currencies[$currencyId] ??= Amount::getTransactionCurrencyById($currencyId);
$return[$currencyId] ??= [
'currency_id' => (string)$currencyId,
'currency_id' => (string) $currencyId,
'currency_code' => $block['currency_code'],
'currency_name' => $block['currency_name'],
'currency_symbol' => $block['currency_symbol'],
'currency_decimal_places' => (int)$block['currency_decimal_places'],
'currency_decimal_places' => (int) $block['currency_decimal_places'],
'start' => $start->toAtomString(),
'end' => $end->toAtomString(),
'budgeted' => '0',
@@ -251,7 +243,7 @@ class BudgetController extends Controller
/** @var array $journal */
foreach ($currentBudgetArray['transaction_journals'] as $journal) {
/** @var numeric-string $amount */
$amount = (string)$journal['amount'];
$amount = (string) $journal['amount'];
$return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $amount);
}
}
@@ -270,14 +262,21 @@ class BudgetController extends Controller
if ($this->convertToPrimary) {
if ($current->transaction_currency_id === $this->primaryCurrency->id) {
// simply add it.
$amount = bcadd($amount, (string)$current->amount);
$amount = bcadd($amount, (string) $current->amount);
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
if ($current->transaction_currency_id !== $this->primaryCurrency->id) {
// convert and then add it.
$converted = $converter->convert($current->transactionCurrency, $this->primaryCurrency, $current->start_date, $current->amount);
$amount = bcadd($amount, $converted);
Log::debug(sprintf('Budgeted in limit #%d: %s %s, converted to %s %s', $current->id, $current->transactionCurrency->code, $current->amount, $this->primaryCurrency->code, $converted));
Log::debug(sprintf(
'Budgeted in limit #%d: %s %s, converted to %s %s',
$current->id,
$current->transactionCurrency->code,
$current->amount,
$this->primaryCurrency->code,
$converted
));
Log::debug(sprintf('Set amount in limit to %s', $amount));
}
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Chart;
use Illuminate\Http\Request;
use Carbon\Carbon;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\DateRangeRequest;
@@ -40,6 +39,7 @@ use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -52,25 +52,23 @@ class CategoryController extends Controller
protected array $acceptedRoles = [UserRoleEnum::READ_ONLY];
private AccountRepositoryInterface $accountRepos;
private AccountRepositoryInterface $accountRepos;
private CurrencyRepositoryInterface $currencyRepos;
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setUserGroup($this->userGroup);
$this->currencyRepos->setUserGroup($this->userGroup);
$this->accountRepos->setUser($this->user);
$this->currencyRepos->setUser($this->user);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setUserGroup($this->userGroup);
$this->currencyRepos->setUserGroup($this->userGroup);
$this->accountRepos->setUser($this->user);
$this->currencyRepos->setUser($this->user);
return $next($request);
}
);
return $next($request);
});
}
/**
@@ -88,7 +86,12 @@ class CategoryController extends Controller
/** @var Carbon $end */
$end = $request->attributes->get('end');
$accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]);
$accounts = $this->accountRepos->getAccountsByType([
AccountTypeEnum::DEBT->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::MORTGAGE->value,
AccountTypeEnum::ASSET->value,
]);
$currencies = [];
$return = [];
$converter = new ExchangeRateConverter();
@@ -104,7 +107,7 @@ class CategoryController extends Controller
/** @var array $journal */
foreach ($journals as $journal) {
// find journal:
$journalCurrencyId = (int)$journal['currency_id'];
$journalCurrencyId = (int) $journal['currency_id'];
$type = $journal['transaction_type_type'];
$currency = $currencies[$journalCurrencyId] ?? $this->currencyRepos->find($journalCurrencyId);
$currencies[$journalCurrencyId] = $currency;
@@ -113,7 +116,7 @@ class CategoryController extends Controller
$currencyCode = $currency->code;
$currencySymbol = $currency->symbol;
$currencyDecimalPlaces = $currency->decimal_places;
$amount = Steam::positive((string)$journal['amount']);
$amount = Steam::positive((string) $journal['amount']);
$pcAmount = null;
// overrule if necessary:
@@ -130,18 +133,17 @@ class CategoryController extends Controller
Log::debug(sprintf('Converted %s %s to %s %s', $journal['currency_code'], $amount, $this->primaryCurrency->code, $pcAmount));
}
$categoryName = $journal['category_name'] ?? (string)trans('firefly.no_category');
$categoryName = $journal['category_name'] ?? (string) trans('firefly.no_category');
$key = sprintf('%s-%s', $categoryName, $currencyCode);
// create arrays
$return[$key] ??= [
'label' => $categoryName,
'currency_id' => (string)$currencyId,
'currency_id' => (string) $currencyId,
'currency_name' => $currencyName,
'currency_code' => $currencyCode,
'currency_symbol' => $currencySymbol,
'currency_decimal_places' => $currencyDecimalPlaces,
'primary_currency_id' => (string)$this->primaryCurrency->id,
'primary_currency_id' => (string) $this->primaryCurrency->id,
'primary_currency_name' => $this->primaryCurrency->name,
'primary_currency_code' => $this->primaryCurrency->code,
'primary_currency_symbol' => $this->primaryCurrency->symbol,
@@ -151,14 +153,8 @@ class CategoryController extends Controller
'end_date' => $end->toAtomString(),
'yAxisID' => 0,
'type' => 'bar',
'entries' => [
'spent' => '0',
'earned' => '0',
],
'pc_entries' => [
'spent' => '0',
'earned' => '0',
],
'entries' => ['spent' => '0', 'earned' => '0'],
'pc_entries' => ['spent' => '0', 'earned' => '0'],
];
// add monies
@@ -182,7 +178,10 @@ class CategoryController extends Controller
$return = array_values($return);
// order by amount
usort($return, static fn (array $a, array $b): int => ((float)$a['entries']['spent'] + (float)$a['entries']['earned']) < ((float)$b['entries']['spent'] + (float)$b['entries']['earned']) ? 1 : -1);
usort($return, static fn (array $a, array $b): int => ((float) $a['entries']['spent'] + (float) $a['entries']['earned'])
< ((float) $b['entries']['spent'] + (float) $b['entries']['earned'])
? 1
: -1);
return response()->json($this->clean($return));
}

View File

@@ -24,9 +24,9 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers;
use Deprecated;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Deprecated;
use FireflyIII\Exceptions\BadHttpHeaderException;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
@@ -63,15 +63,16 @@ abstract class Controller extends BaseController
use ValidatesRequests;
use ValidatesUserGroupTrait;
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string JSON_CONTENT_TYPE = 'application/json';
protected array $accepts = ['application/json', 'application/vnd.api+json'];
protected const string CONTENT_TYPE = 'application/vnd.api+json';
protected const string JSON_CONTENT_TYPE = 'application/json';
protected bool $convertToPrimary = false;
protected array $accepts = ['application/json', 'application/vnd.api+json'];
protected bool $convertToPrimary = false;
protected TransactionCurrency $primaryCurrency;
/** @deprecated use Request classes */
protected ParameterBag $parameters;
protected ParameterBag $parameters;
/**
* Controller constructor.
@@ -79,26 +80,22 @@ abstract class Controller extends BaseController
public function __construct()
{
// get global parameters
$this->middleware(
function ($request, $next) {
$this->parameters = $this->getParameters();
if (auth()->check()) {
$language = Steam::getLanguage();
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
app()->setLocale($language);
}
// filter down what this endpoint accepts.
if (!$request->accepts($this->accepts)) {
throw new BadHttpHeaderException(sprintf('Sorry, Accept header "%s" is not something this endpoint can provide.', $request->header('Accept')));
}
return $next($request);
$this->middleware(function ($request, $next) {
$this->parameters = $this->getParameters();
if (auth()->check()) {
$language = Steam::getLanguage();
$this->convertToPrimary = Amount::convertToPrimary();
$this->primaryCurrency = Amount::getPrimaryCurrency();
app()->setLocale($language);
}
);
// filter down what this endpoint accepts.
if (!$request->accepts($this->accepts)) {
throw new BadHttpHeaderException(sprintf('Sorry, Accept header "%s" is not something this endpoint can provide.', $request->header('Accept')));
}
return $next($request);
});
}
#[Deprecated(message: <<<'TXT'
@@ -108,7 +105,7 @@ abstract class Controller extends BaseController
private function getParameters(): ParameterBag
{
$bag = new ParameterBag();
$page = (int)request()->get('page');
$page = (int) request()->get('page');
$page = min(max(1, $page), 2 ** 16);
$bag->set('page', $page);
@@ -127,10 +124,10 @@ abstract class Controller extends BaseController
$obj = null;
if (null !== $date) {
try {
$obj = Carbon::parse((string)$date, config('app.timezone'));
$obj = Carbon::parse((string) $date, config('app.timezone'));
} catch (InvalidFormatException $e) {
// don't care
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string)$date, 0, 20), $e->getMessage()));
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', substr((string) $date, 0, 20), $e->getMessage()));
}
}
if ($obj instanceof Carbon) {
@@ -150,24 +147,27 @@ abstract class Controller extends BaseController
$value = null;
}
if (null !== $value) {
$value = (int)$value;
$value = (int) $value;
$value = min(max(1, $value), 2 ** 16);
$bag->set($integer, $value);
}
if (null === $value
if (
null === $value
&& 'limit' === $integer // @phpstan-ignore-line
&& auth()->check()) {
&& auth()->check()
) {
// set default for user:
/** @var User $user */
$user = auth()->user();
$pageSize = (int)Preferences::getForUser($user, 'listPageSize', 50)->data;
$pageSize = (int) Preferences::getForUser($user, 'listPageSize', 50)->data;
$bag->set($integer, $pageSize);
}
}
// sort fields:
return $bag;
// return $this->getSortParameters($bag);
}

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Data;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Data\DestroyRequest;
use FireflyIII\Enums\AccountTypeEnum;
@@ -49,6 +48,7 @@ use FireflyIII\Services\Internal\Destroy\AccountDestroyService;
use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
/**
@@ -63,13 +63,11 @@ class DestroyController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
return $next($request);
}
);
return $next($request);
});
}
public function destroy(DestroyRequest $request): JsonResponse
@@ -77,10 +75,40 @@ class DestroyController extends Controller
$objects = $request->getObjects();
$this->unused = $request->boolean('unused');
$allExceptAssets = [AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::RECONCILIATION->value, AccountTypeEnum::REVENUE->value];
$all = [AccountTypeEnum::ASSET->value, AccountTypeEnum::BENEFICIARY->value, AccountTypeEnum::CASH->value, AccountTypeEnum::CREDITCARD->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::EXPENSE->value, AccountTypeEnum::IMPORT->value, AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::LIABILITY_CREDIT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::RECONCILIATION->value];
$allExceptAssets = [
AccountTypeEnum::BENEFICIARY->value,
AccountTypeEnum::CASH->value,
AccountTypeEnum::CREDITCARD->value,
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::IMPORT->value,
AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::LIABILITY_CREDIT->value,
AccountTypeEnum::RECONCILIATION->value,
AccountTypeEnum::REVENUE->value,
];
$all = [
AccountTypeEnum::ASSET->value,
AccountTypeEnum::BENEFICIARY->value,
AccountTypeEnum::CASH->value,
AccountTypeEnum::CREDITCARD->value,
AccountTypeEnum::DEBT->value,
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::EXPENSE->value,
AccountTypeEnum::IMPORT->value,
AccountTypeEnum::INITIAL_BALANCE->value,
AccountTypeEnum::LIABILITY_CREDIT->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::MORTGAGE->value,
AccountTypeEnum::RECONCILIATION->value,
];
$liabilities = [AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::CREDITCARD->value];
$transactions = [TransactionTypeEnum::WITHDRAWAL->value, TransactionTypeEnum::DEPOSIT->value, TransactionTypeEnum::TRANSFER->value, TransactionTypeEnum::RECONCILIATION->value];
$transactions = [
TransactionTypeEnum::WITHDRAWAL->value,
TransactionTypeEnum::DEPOSIT->value,
TransactionTypeEnum::TRANSFER->value,
TransactionTypeEnum::RECONCILIATION->value,
];
match ($objects) {
'budgets' => $this->destroyBudgets(),
@@ -101,7 +129,7 @@ class DestroyController extends Controller
'withdrawals' => $this->destroyTransactions([TransactionTypeEnum::WITHDRAWAL->value]),
'deposits' => $this->destroyTransactions([TransactionTypeEnum::DEPOSIT->value]),
'transfers' => $this->destroyTransactions([TransactionTypeEnum::TRANSFER->value]),
default => throw new FireflyException(sprintf('200033: This endpoint can\'t handle object "%s"', $objects)),
default => throw new FireflyException(sprintf('200033: This endpoint can\'t handle object "%s"', $objects))
};
Preferences::mark();

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Data;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\Account;
@@ -40,6 +39,7 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
* Class PurgeController
@@ -51,13 +51,11 @@ class PurgeController extends Controller
public function __construct()
{
parent::__construct();
$this->middleware(
function (Request $request, $next) {
$this->validateUserGroup($request);
$this->middleware(function (Request $request, $next) {
$this->validateUserGroup($request);
return $next($request);
}
);
return $next($request);
});
}
/**

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Http\Request;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
@@ -45,7 +44,8 @@ use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\ValidationException;
use League\Fractal\Resource\Item;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
/**
* Class StoreController
@@ -84,7 +84,7 @@ class StoreController extends Controller
*
* Store a new transaction.
*
* @throws FireflyException|ValidationException
* @throws FireflyException|GoneHttpException|ValidationException
*/
public function store(StoreRequest $request): JsonResponse
{
@@ -133,10 +133,9 @@ class StoreController extends Controller
->withAPIInformation()
;
/** @var TransactionGroup $selectedGroup */
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new NotFoundHttpException();
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
// enrich

View File

@@ -262,7 +262,7 @@ class ListController extends Controller
// filter selection
$collection = $unfiltered->filter(
static function (Recurrence $recurrence) use ($currency): ?Recurrence { // @phpstan-ignore-line
if (array_any($recurrence->recurrenceTransactions, fn ($transaction): bool => $transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id)) {
if (array_any($recurrence->recurrenceTransactions, static fn ($transaction): bool => $transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id)) {
return $recurrence;
}
@@ -311,7 +311,7 @@ class ListController extends Controller
$collection = $unfiltered->filter(
static function (Rule $rule) use ($currency): ?Rule { // @phpstan-ignore-line
if (array_any($rule->ruleTriggers, fn ($trigger): bool => 'currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value)) {
if (array_any($rule->ruleTriggers, static fn ($trigger): bool => 'currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value)) {
return $rule;
}

View File

@@ -91,7 +91,7 @@ class ConvertsDatesToUTC extends Command
}
$this->friendlyInfo(sprintf('Converting field "%s" of model "%s" to UTC.', $field, $shortModel));
$items->each(
function ($item) use ($field, $timezoneField): void {
static function ($item) use ($field, $timezoneField): void {
$date = Carbon::parse($item->{$field}, $item->{$timezoneField}); // @phpstan-ignore-line
$date->setTimezone('UTC');
$item->{$field} = $date->format('Y-m-d H:i:s'); // @phpstan-ignore-line

View File

@@ -103,7 +103,7 @@ class CorrectsPrimaryCurrencyAmounts extends Command
private function recalculateAccounts(UserGroup $userGroup): void
{
$set = $userGroup->accounts()->where(function (EloquentBuilder $q): void {
$set = $userGroup->accounts()->where(static function (EloquentBuilder $q): void {
$q->whereNotNull('virtual_balance');
// this needs a different piece of code for postgres.
@@ -226,10 +226,10 @@ class CorrectsPrimaryCurrencyAmounts extends Command
$set = DB::table('transactions')
->join('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->where('transaction_journals.user_group_id', $userGroup->id)
->where(function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(function (DatabaseBuilder $q2) use ($currency): void {
->where(static function (DatabaseBuilder $q1) use ($currency): void {
$q1->where(static function (DatabaseBuilder $q2) use ($currency): void {
$q2->whereNot('transactions.transaction_currency_id', $currency->id)->whereNull('transactions.foreign_currency_id');
})->orWhere(function (DatabaseBuilder $q3) use ($currency): void {
})->orWhere(static function (DatabaseBuilder $q3) use ($currency): void {
$q3->whereNot('transactions.transaction_currency_id', $currency->id)->whereNot('transactions.foreign_currency_id', $currency->id);
});
})

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
/*
* ExplainAvailableBudget.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Console\Commands\Explain;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Console\Commands\VerifiesAccessToken;
use FireflyIII\Support\Facades\Navigation;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Console\Command;
class ExplainAvailableBudget extends Command
{
use VerifiesAccessToken;
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'explain:available-budget
{--date=now : A date formatted YYYY-MM-DD or the word "now"}
{--user=1 : The user ID.}
{--token= : The user\'s access token.}
';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Explains why the available budget amount is what it is.';
/**
* Execute the console command.
*/
public function handle(): int
{
$date = $this->getDate((string) $this->option('date'));
$range = Preferences::getForUser($this->getUser(), 'viewRange', '1M')->data ?? '1M';
$title = Navigation::periodShow($date, $range);
$this->line('This command explains why the "available" budget bar at the top of your /budget bar means.');
$this->line(sprintf(
'You submitted date %s and your settings show a %s period, so this explanation concerns the period "%s".',
$date->format('Y-m-d'),
$range,
$title
));
return Command::SUCCESS;
}
private function getDate(string $param): Carbon
{
if ('now' === $param) {
return today();
}
try {
$date = Carbon::parse($param);
} catch (InvalidFormatException) {
$this->warn('Invalid date given. Fall back to today\'s date.');
return today();
}
return $date;
}
}

View File

@@ -26,6 +26,7 @@ namespace FireflyIII\Events;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
use SensitiveParameter;
/**
* Class RequestedNewPassword.
@@ -46,7 +47,7 @@ class RequestedNewPassword extends Event
/**
* Create a new event instance. This event is triggered when a users tries to reset his or her password.
*/
public function __construct(User $user, string $token, string $ipAddress)
public function __construct(User $user, #[SensitiveParameter] string $token, string $ipAddress)
{
$this->user = $user;
$this->token = $token;

View File

@@ -45,6 +45,7 @@ use Override;
use Symfony\Component\HttpFoundation\Exception\SuspiciousOperationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\GoneHttpException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
@@ -71,6 +72,7 @@ class Handler extends ExceptionHandler
AuthenticationException::class,
LaravelValidationException::class,
NotFoundHttpException::class,
GoneHttpException::class,
OAuthServerException::class,
LaravelOAuthException::class,
TokenMismatchException::class,

View File

@@ -70,8 +70,12 @@ class UpdatedGroupEventHandler
foreach ($event->transactionGroup->transactionJournals as $journal) {
$source = $journal->transactions()->where('amount', '<', '0')->first();
$dest = $journal->transactions()->where('amount', '>', '0')->first();
$repository->deleteStatisticsForModel($source->account, $journal->date);
$repository->deleteStatisticsForModel($dest->account, $journal->date);
if (null !== $source) {
$repository->deleteStatisticsForModel($source->account, $journal->date);
}
if (null !== $dest) {
$repository->deleteStatisticsForModel($dest->account, $journal->date);
}
$categories = $journal->categories;
$tags = $journal->tags;

View File

@@ -828,7 +828,7 @@ class GroupCollector implements GroupCollectorInterface
*/
foreach ($this->sorting as $field => $direction) {
$func = 'ASC' === $direction ? 'sortBy' : 'sortByDesc';
$collection = $collection->{$func}(function (array $product, int $key) use ($field) { // @phpstan-ignore-line
$collection = $collection->{$func}(static function (array $product, int $key) use ($field) { // @phpstan-ignore-line
// depends on $field:
if ('description' === $field) {
if (1 === count($product['transactions'])) {

View File

@@ -37,6 +37,7 @@ use Illuminate\View\View;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use SensitiveParameter;
/**
* Class ResetPasswordController
@@ -97,7 +98,7 @@ class ResetPasswordController extends Controller
// database. Otherwise, we will parse the error and return the response.
$response = $this->broker()->reset(
$this->credentials($request),
function ($user, $password): void {
function ($user, #[SensitiveParameter] $password): void {
$this->resetPassword($user, $password);
}
);
@@ -123,7 +124,7 @@ class ResetPasswordController extends Controller
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function showResetForm(Request $request, $token = null)
public function showResetForm(Request $request, #[SensitiveParameter] $token = null)
{
if ('web' !== config('firefly.authentication_guard')) {
$message = sprintf('Cannot reset password when authenticating over "%s".', config('firefly.authentication_guard'));

View File

@@ -29,7 +29,6 @@ use FireflyIII\Support\Facades\Navigation;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Support\JsonApi\Enrichments\SubscriptionEnrichment;
@@ -185,7 +184,7 @@ class ShowController extends Controller
/** @var AttachmentTransformer $transformer */
$transformer = app(AttachmentTransformer::class);
$attachments = $collection->each(
static fn (Attachment $attachment) => $transformer->transform($attachment)
$transformer->transform(...)
);
}

View File

@@ -41,7 +41,7 @@ class IndexController extends Controller
// translations:
$this->middleware(
function ($request, $next) {
static function ($request, $next) {
app('view')->share('mainTitleIcon', 'fa-exchange');
app('view')->share('title', (string) trans('firefly.header_exchange_rates'));

View File

@@ -52,6 +52,7 @@ use Illuminate\View\View;
use Laravel\Passport\ClientRepository;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use SensitiveParameter;
/**
* Class ProfileController.
@@ -91,7 +92,7 @@ class ProfileController extends Controller
*
* @throws FireflyException
*/
public function confirmEmailChange(UserRepositoryInterface $repository, string $token): Redirector|RedirectResponse
public function confirmEmailChange(UserRepositoryInterface $repository, #[SensitiveParameter] string $token): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
throw new FireflyException(trans('firefly.external_user_mgt_disabled'));
@@ -388,7 +389,7 @@ class ProfileController extends Controller
*
* @throws FireflyException
*/
public function undoEmailChange(UserRepositoryInterface $repository, string $token, string $hash): Redirector|RedirectResponse
public function undoEmailChange(UserRepositoryInterface $repository, #[SensitiveParameter] string $token, string $hash): Redirector|RedirectResponse
{
if (!$this->internalAuth) {
throw new FireflyException(trans('firefly.external_user_mgt_disabled'));

View File

@@ -157,7 +157,7 @@ class CreateRecurringTransactions implements ShouldQueue
private function filterRecurrences(Collection $recurrences): Collection
{
return $recurrences->filter(
fn (Recurrence $recurrence): bool => $this->validRecurrence($recurrence)
$this->validRecurrence(...)
);
}

View File

@@ -53,6 +53,6 @@ class AccountMeta extends Model
protected function data(): Attribute
{
return Attribute::make(get: fn (mixed $value): string => (string)json_decode((string)$value, true), set: fn (mixed $value): array => ['data' => json_encode($value)]);
return Attribute::make(get: static fn (mixed $value): string => (string)json_decode((string)$value, true), set: static fn (mixed $value): array => ['data' => json_encode($value)]);
}
}

View File

@@ -103,16 +103,16 @@ class AvailableBudget extends Model
protected function endDate(): Attribute
{
return Attribute::make(
get: fn (string $value): Carbon => Carbon::parse($value),
set: fn (Carbon $value): string => $value->format('Y-m-d'),
get: static fn (string $value): Carbon => Carbon::parse($value),
set: static fn (Carbon $value): string => $value->format('Y-m-d'),
);
}
protected function startDate(): Attribute
{
return Attribute::make(
get: fn (string $value): Carbon => Carbon::parse($value),
set: fn (Carbon $value): string => $value->format('Y-m-d'),
get: static fn (string $value): Carbon => Carbon::parse($value),
set: static fn (Carbon $value): string => $value->format('Y-m-d'),
);
}

View File

@@ -52,6 +52,6 @@ class Configuration extends Model
*/
protected function data(): Attribute
{
return Attribute::make(get: fn ($value): mixed => json_decode((string)$value), set: fn ($value): array => ['data' => json_encode($value)]);
return Attribute::make(get: static fn ($value): mixed => json_decode((string)$value), set: static fn ($value): array => ['data' => json_encode($value)]);
}
}

View File

@@ -113,7 +113,7 @@ class Rule extends Model
protected function description(): Attribute
{
return Attribute::make(set: fn ($value): array => ['description' => e($value)]);
return Attribute::make(set: static fn ($value): array => ['description' => e($value)]);
}
protected function order(): Attribute

View File

@@ -57,7 +57,7 @@ class TransactionJournalMeta extends Model
protected function data(): Attribute
{
return Attribute::make(get: fn ($value): mixed => json_decode((string)$value, false), set: function ($value): array {
return Attribute::make(get: static fn ($value): mixed => json_decode((string)$value, false), set: static function ($value): array {
$data = json_encode($value);
return ['data' => $data, 'hash' => hash('sha256', $data)];

View File

@@ -45,7 +45,7 @@ class AppServiceProvider extends ServiceProvider
{
Schema::defaultStringLength(191);
// Passport::$clientUuids = false;
Response::macro('api', function (array $value) {
Response::macro('api', static function (array $value) {
$headers = [
'Cache-Control' => 'no-store',
];
@@ -61,7 +61,7 @@ class AppServiceProvider extends ServiceProvider
});
// blade extension
Blade::directive('activeXRoutePartial', function (string $route): string {
Blade::directive('activeXRoutePartial', static function (string $route): string {
$name = Route::getCurrentRoute()->getName() ?? '';
if (str_contains($name, $route)) {
return 'menu-open';
@@ -69,7 +69,7 @@ class AppServiceProvider extends ServiceProvider
return '';
});
Blade::if('partialroute', function (string $route, string $firstParam = ''): bool {
Blade::if('partialroute', static function (string $route, string $firstParam = ''): bool {
$name = Route::getCurrentRoute()->getName() ?? '';
if ('' === $firstParam && str_contains($name, $route)) {
return true;

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Providers;
use FireflyIII\Support\Search\OperatorQuerySearch;
use FireflyIII\Support\Search\QueryParser\GdbotsQueryParser;
use FireflyIII\Support\Search\QueryParser\QueryParser;
use FireflyIII\Support\Search\QueryParser\QueryParserInterface;
use FireflyIII\Support\Search\SearchInterface;
@@ -49,16 +48,7 @@ class SearchServiceProvider extends ServiceProvider
public function register(): void
{
$this->app->bind(
static function (): QueryParserInterface {
return app(QueryParser::class);
// 2025-12-20 ignore this setting.
// $implementation = config('search.query_parser');
//
// return match ($implementation) {
// 'new' => app(QueryParser::class),
// default => app(GdbotsQueryParser::class),
// };
}
static fn (): QueryParserInterface => app(QueryParser::class)
);
$this->app->bind(

View File

@@ -55,12 +55,12 @@ class ExchangeRateRepository implements ExchangeRateRepositoryInterface, UserGro
// orderBy('date', 'DESC')->toRawSql();
return
$this->userGroup->currencyExchangeRates()
->where(function (Builder $q1) use ($from, $to): void {
$q1->where(function (Builder $q) use ($from, $to): void {
->where(static function (Builder $q1) use ($from, $to): void {
$q1->where(static function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $from->id)
->where('to_currency_id', $to->id)
;
})->orWhere(function (Builder $q) use ($from, $to): void {
})->orWhere(static function (Builder $q) use ($from, $to): void {
$q->where('from_currency_id', $to->id)
->where('to_currency_id', $from->id)
;

View File

@@ -39,6 +39,7 @@ use Illuminate\Database\QueryException;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Override;
use SensitiveParameter;
/**
* Class UserRepository.
@@ -74,7 +75,7 @@ class UserRepository implements UserRepositoryInterface
return true;
}
public function changePassword(User $user, string $password): bool
public function changePassword(User $user, #[SensitiveParameter] string $password): bool
{
$user->password = bcrypt($password);
$user->save();

View File

@@ -30,6 +30,7 @@ use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Collection;
use SensitiveParameter;
/**
* Interface UserRepositoryInterface.
@@ -64,7 +65,7 @@ interface UserRepositoryInterface
/**
* @return mixed
*/
public function changePassword(User $user, string $password);
public function changePassword(User $user, #[SensitiveParameter] string $password);
public function changeStatus(User $user, bool $isBlocked, string $code): bool;

View File

@@ -27,6 +27,7 @@ use Illuminate\Support\Facades\Log;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
use SensitiveParameter;
/**
* Class PwndVerifierV2.
@@ -36,7 +37,7 @@ class PwndVerifierV2 implements Verifier
/**
* Verify the given password against (some) service.
*/
public function validPassword(string $password): bool
public function validPassword(#[SensitiveParameter] string $password): bool
{
// Yes SHA1 is unsafe but in this context its fine.
$hash = sha1($password);

View File

@@ -23,6 +23,8 @@ declare(strict_types=1);
namespace FireflyIII\Services\Password;
use SensitiveParameter;
/**
* Interface Verifier.
*/
@@ -31,5 +33,5 @@ interface Verifier
/**
* Verify the given password against (some) service.
*/
public function validPassword(string $password): bool;
public function validPassword(#[SensitiveParameter] string $password): bool;
}

View File

@@ -33,6 +33,7 @@ use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Support\Str;
use Override;
use SensitiveParameter;
/**
* Class RemoteUserProvider
@@ -100,7 +101,7 @@ class RemoteUserProvider implements UserProvider
*
* @SuppressWarnings("PHPMD.UnusedFormalParameter")
*/
public function retrieveByToken($identifier, $token): ?Authenticatable
public function retrieveByToken($identifier, #[SensitiveParameter] $token): ?Authenticatable
{
Log::debug(sprintf('Now at %s', __METHOD__));
@@ -114,7 +115,7 @@ class RemoteUserProvider implements UserProvider
*
* @throws FireflyException
*/
public function updateRememberToken(Authenticatable $user, $token): void
public function updateRememberToken(Authenticatable $user, #[SensitiveParameter] $token): void
{
Log::debug(sprintf('Now at %s', __METHOD__));

View File

@@ -331,7 +331,7 @@ trait PeriodOverview
}
return $this->statistics->filter(
fn (PeriodStatistic $statistic): bool => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
static fn (PeriodStatistic $statistic): bool => $statistic->start->eq($start) && $statistic->end->eq($end) && $statistic->type === $type
);
}
@@ -344,7 +344,7 @@ trait PeriodOverview
}
return $this->statistics->filter(
fn (PeriodStatistic $statistic): bool => $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix)
static fn (PeriodStatistic $statistic): bool => $statistic->start->eq($start) && $statistic->end->eq($end) && str_starts_with($statistic->type, $prefix)
);
}

View File

@@ -179,7 +179,7 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function filterToBudget(array $expenses, int $budget): array
{
$result = array_filter($expenses, fn (array $item): bool => (int)$item['budget_id'] === $budget);
$result = array_filter($expenses, static fn (array $item): bool => (int)$item['budget_id'] === $budget);
Log::debug(sprintf('filterToBudget for budget #%d, from %d to %d items', $budget, count($expenses), count($result)));
return $result;
@@ -187,13 +187,13 @@ class BudgetLimitEnrichment implements EnrichmentInterface
private function stringifyIds(): void
{
$this->expenses = array_map(fn ($first): array => array_map(function (array $second): array {
$this->expenses = array_map(static fn ($first): array => array_map(static function (array $second): array {
$second['currency_id'] = (string)($second['currency_id'] ?? 0);
return $second;
}, $first), $this->expenses);
$this->pcExpenses = array_map(fn (array $first): array => array_map(function (array $second): array {
$this->pcExpenses = array_map(static fn (array $first): array => array_map(static function (array $second): array {
$second['currency_id'] ??= 0;
return $second;

View File

@@ -339,7 +339,7 @@ class RecurringEnrichment implements EnrichmentInterface
/** @var RecurrenceRepetition $repetition */
foreach ($set as $repetition) {
$recurrence = $this->collection->filter(fn (Recurrence $item): bool => (int)$item->id === (int)$repetition->recurrence_id)->first();
$recurrence = $this->collection->filter(static fn (Recurrence $item): bool => (int)$item->id === (int)$repetition->recurrence_id)->first();
$fromDate = clone ($recurrence->latest_date ?? $recurrence->first_date);
$recurrenceId = (int)$repetition->recurrence_id;
$repId = (int)$repetition->id;

View File

@@ -173,7 +173,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
*/
protected function lastPaidDate(Bill $subscription, Collection $dates, Carbon $default): Carbon
{
$filtered = $dates->filter(fn (TransactionJournal $journal): bool => (int)$journal->bill_id === (int)$subscription->id);
$filtered = $dates->filter(static fn (TransactionJournal $journal): bool => (int)$journal->bill_id === (int)$subscription->id);
Log::debug(sprintf('Filtered down from %d to %d entries for bill #%d.', $dates->count(), $filtered->count(), $subscription->id));
if (0 === $filtered->count()) {
return $default;
@@ -294,7 +294,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
// At this point the "next match" is exactly after the last time the bill was paid.
$result = [];
$filtered = $set->filter(fn (TransactionJournal $journal): bool => (int)$journal->bill_id === (int)$subscription->id);
$filtered = $set->filter(static fn (TransactionJournal $journal): bool => (int)$journal->bill_id === (int)$subscription->id);
foreach ($filtered as $entry) {
$array = [
'transaction_group_id' => (string)$entry->transaction_group_id,
@@ -385,7 +385,7 @@ class SubscriptionEnrichment implements EnrichmentInterface
private function filterPaidDates(array $entries): array
{
return array_map(function (array $entry): array {
return array_map(static function (array $entry): array {
unset($entry['date_object']);
return $entry;

View File

@@ -49,7 +49,7 @@ class Preferences
return Preference::where('user_id', $user->id)
->where('name', '!=', 'currencyPreference')
->where(function (Builder $q) use ($user): void {
->where(static function (Builder $q) use ($user): void {
$q->whereNull('user_group_id');
$q->orWhere('user_group_id', $user->user_group_id);
})
@@ -108,7 +108,7 @@ class Preferences
{
$result = [];
$preferences = Preference::where('user_id', $user->id)
->where(function (Builder $q) use ($user): void {
->where(static function (Builder $q) use ($user): void {
$q->whereNull('user_group_id');
$q->orWhere('user_group_id', $user->user_group_id);
})

View File

@@ -32,9 +32,9 @@ use Illuminate\Support\Facades\Log;
class TransactionSummarizer
{
private bool $convertToPrimary = false;
private bool $convertToPrimary = false;
private TransactionCurrency $default;
private User $user;
private User $user;
public function __construct(?User $user = null)
{
@@ -51,7 +51,7 @@ class TransactionSummarizer
$field = 'amount';
// grab default currency information.
$currencyId = (int)$journal['currency_id'];
$currencyId = (int) $journal['currency_id'];
$currencyName = $journal['currency_name'];
$currencySymbol = $journal['currency_symbol'];
$currencyCode = $journal['currency_code'];
@@ -67,8 +67,8 @@ class TransactionSummarizer
if ($this->convertToPrimary) {
// Log::debug('convertToPrimary is true.');
// if convert to primary currency, use the primary currency amount yes or no?
$usePrimary = $this->default->id !== (int)$journal['currency_id'];
$useForeign = $this->default->id === (int)$journal['foreign_currency_id'];
$usePrimary = $this->default->id !== (int) $journal['currency_id'];
$useForeign = $this->default->id === (int) $journal['foreign_currency_id'];
if ($usePrimary) {
// Log::debug(sprintf('Journal #%d switches to primary currency amount (original is %s)', $journal['transaction_journal_id'], $journal['currency_code']));
$field = 'pc_amount';
@@ -81,7 +81,7 @@ class TransactionSummarizer
if ($useForeign) {
// Log::debug(sprintf('Journal #%d switches to foreign amount (foreign is %s)', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
$field = 'foreign_amount';
$currencyId = (int)$journal['foreign_currency_id'];
$currencyId = (int) $journal['foreign_currency_id'];
$currencyName = $journal['foreign_currency_name'];
$currencySymbol = $journal['foreign_currency_symbol'];
$currencyCode = $journal['foreign_currency_code'];
@@ -91,9 +91,13 @@ class TransactionSummarizer
if (!$this->convertToPrimary) {
// Log::debug('convertToPrimary is false.');
// use foreign amount?
$foreignCurrencyId = (int)$journal['foreign_currency_id'];
$foreignCurrencyId = (int) $journal['foreign_currency_id'];
if (0 !== $foreignCurrencyId) {
Log::debug(sprintf('Journal #%d also includes foreign amount (foreign is "%s")', $journal['transaction_journal_id'], $journal['foreign_currency_code']));
Log::debug(sprintf(
'Journal #%d also includes foreign amount (foreign is "%s")',
$journal['transaction_journal_id'],
$journal['foreign_currency_code']
));
$foreignCurrencyName = $journal['foreign_currency_name'];
$foreignCurrencySymbol = $journal['foreign_currency_symbol'];
$foreignCurrencyCode = $journal['foreign_currency_code'];
@@ -102,7 +106,7 @@ class TransactionSummarizer
}
// first process normal amount
$amount = (string)($journal[$field] ?? '0');
$amount = (string) ($journal[$field] ?? '0');
$array[$currencyId] ??= [
'sum' => '0',
'currency_id' => $currencyId,
@@ -121,7 +125,7 @@ class TransactionSummarizer
// then process foreign amount, if it exists.
if (0 !== $foreignCurrencyId && $includeForeign) {
$amount = (string)($journal['foreign_amount'] ?? '0');
$amount = (string) ($journal['foreign_amount'] ?? '0');
$array[$foreignCurrencyId] ??= [
'sum' => '0',
'currency_id' => $foreignCurrencyId,
@@ -149,14 +153,12 @@ class TransactionSummarizer
public function groupByDirection(array $journals, string $method, string $direction): array
{
$array = [];
$idKey = sprintf('%s_account_id', $direction);
$nameKey = sprintf('%s_account_name', $direction);
$convertToPrimary = Amount::convertToPrimary($this->user);
$primary = Amount::getPrimaryCurrencyByUserGroup($this->user->userGroup);
Log::debug(sprintf('groupByDirection(array, %s, %s).', $direction, $method));
foreach ($journals as $journal) {
// currency
@@ -193,13 +195,25 @@ class TransactionSummarizer
];
// add the data from the $field to the array.
$array[$key]['sum'] = bcadd($array[$key]['sum'], (string) Steam::{$method}((string)($journal[$field] ?? '0'))); // @phpstan-ignore-line
Log::debug(sprintf('Field for transaction #%d is "%s" (%s). Sum: %s', $journal['transaction_group_id'], $currencyCode, $field, $array[$key]['sum']));
$array[$key]['sum'] = bcadd($array[$key]['sum'], (string) Steam::{$method}((string) ($journal[$field] ?? '0'))); // @phpstan-ignore-line
Log::debug(sprintf(
'Field for transaction #%d is "%s" (%s). Sum: %s',
$journal['transaction_group_id'],
$currencyCode,
$field,
$array[$key]['sum']
));
// also do foreign amount, but only when convertToPrimary is false (otherwise we have it already)
// or when convertToPrimary is true and the foreign currency is ALSO not the default currency.
if ((!$convertToPrimary || $journal['foreign_currency_id'] !== $primary->id) && 0 !== (int)$journal['foreign_currency_id']) {
Log::debug(sprintf('Use foreign amount from transaction #%d: %s %s. Sum: %s', $journal['transaction_group_id'], $currencyCode, $journal['foreign_amount'], $array[$key]['sum']));
if ((!$convertToPrimary || $journal['foreign_currency_id'] !== $primary->id) && 0 !== (int) $journal['foreign_currency_id']) {
Log::debug(sprintf(
'Use foreign amount from transaction #%d: %s %s. Sum: %s',
$journal['transaction_group_id'],
$currencyCode,
$journal['foreign_amount'],
$array[$key]['sum']
));
$key = sprintf('%s-%s', $journal[$idKey], $journal['foreign_currency_id']);
$array[$key] ??= [
'id' => $journal[$idKey],
@@ -211,7 +225,7 @@ class TransactionSummarizer
'currency_code' => $journal['foreign_currency_code'],
'currency_decimal_places' => $journal['foreign_currency_decimal_places'],
];
$array[$key]['sum'] = bcadd($array[$key]['sum'], (string) Steam::{$method}((string)$journal['foreign_amount'])); // @phpstan-ignore-line
$array[$key]['sum'] = bcadd($array[$key]['sum'], (string) Steam::{$method}((string) $journal['foreign_amount'])); // @phpstan-ignore-line
}
}

View File

@@ -35,7 +35,7 @@ trait ValidatesWebhooks
public function withValidator(Validator $validator): void
{
$validator->after(
function (Validator $validator): void {
static function (Validator $validator): void {
Log::debug('Validating webhook');
if (count($validator->failed()) > 0) {
return;

View File

@@ -1245,9 +1245,9 @@ class OperatorQuerySearch implements SearchInterface
return false;
//
// all account related searches:
//
case 'account_is':
$this->searchAccount($value, SearchDirection::BOTH, StringPosition::IS);
@@ -1608,9 +1608,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// cash account
//
case 'source_is_cash':
$account = $this->getCashAccount();
$this->collector->setSourceAccounts(new Collection()->push($account));
@@ -1647,9 +1647,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// description
//
case 'description_starts':
$this->collector->descriptionStarts([$value]);
@@ -1690,9 +1690,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// currency
//
case 'currency_is':
$currency = $this->findCurrency($value);
if ($currency instanceof TransactionCurrency) {
@@ -1741,9 +1741,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// attachments
//
case 'has_attachments':
case '-has_no_attachments':
Log::debug('Set collector to filter on attachments.');
@@ -1758,7 +1758,7 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// categories
case '-has_any_category':
case 'has_no_category':
@@ -1866,9 +1866,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// budgets
//
case '-has_any_budget':
case 'has_no_budget':
$this->collector->withoutBudget();
@@ -1977,9 +1977,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// bill
//
case '-has_any_bill':
case 'has_no_bill':
$this->collector->withoutBill();
@@ -2088,9 +2088,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// tags
//
case '-has_any_tag':
case 'has_no_tag':
$this->collector->withoutTags();
@@ -2216,9 +2216,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// notes
//
case 'notes_contains':
$this->collector->notesContain($value);
@@ -2281,9 +2281,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// amount
//
case 'amount_is':
// strip comma's, make dots.
Log::debug(sprintf('Original value "%s"', $value));
@@ -2368,9 +2368,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// transaction type
//
case 'transaction_type':
$this->collector->setTypes([ucfirst($value)]);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $value));
@@ -2383,9 +2383,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// dates
//
case '-date_on':
case 'date_on':
$range = $this->parseDateRange($operator, $value);
@@ -2581,9 +2581,9 @@ class OperatorQuerySearch implements SearchInterface
return false;
//
// external URL
//
case '-any_external_url':
case 'no_external_url':
$this->collector->withoutExternalUrl();
@@ -2648,9 +2648,9 @@ class OperatorQuerySearch implements SearchInterface
break;
//
// other fields
//
case 'external_id_is':
$this->collector->setExternalId($value);

View File

@@ -26,6 +26,7 @@ declare(strict_types=1);
namespace FireflyIII\Support\Search\QueryParser;
use Illuminate\Support\Facades\Log;
use SensitiveParameter;
/**
* Single-pass parser that processes query strings into structured nodes.
@@ -202,7 +203,7 @@ class QueryParser implements QueryParserInterface
return new NodeGroup($nodes, $prohibited);
}
private function createNode(string $token, string $fieldName, bool $prohibited): Node
private function createNode(#[SensitiveParameter] string $token, string $fieldName, bool $prohibited): Node
{
if ('' !== $fieldName) {
// OK dus hoe trim je \" correct?

View File

@@ -80,7 +80,7 @@ class Steam
$currency = $currencies[$account->id];
// second array
$accountSums = array_filter($arrayOfSums, fn (array $entry): bool => $entry['account_id'] === $account->id);
$accountSums = array_filter($arrayOfSums, static fn (array $entry): bool => $entry['account_id'] === $account->id);
if (0 === count($accountSums)) {
$result[$account->id] = $return;

View File

@@ -392,7 +392,7 @@ class General extends AbstractExtension
{
return new TwigFunction(
'phpdate',
static fn (string $str): string => date($str)
date(...)
);
}
@@ -400,9 +400,7 @@ class General extends AbstractExtension
{
return new TwigFunction(
'fireflyiiiconfig',
static function (string $string, mixed $default): mixed {
return FireflyConfig::get($string, $default)->data;
}
static fn (string $string, mixed $default): mixed => FireflyConfig::get($string, $default)->data
);
}
}

View File

@@ -34,7 +34,7 @@ class ActionExpressionLanguageProvider implements ExpressionFunctionProviderInte
{
public function getFunctions(): array
{
$function = function ($arguments, $str): string {
$function = static function ($arguments, $str): string {
if (!is_string($str)) {
return (string) $str;
}

View File

@@ -425,9 +425,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getSourceTransaction(TransactionJournal $journal): Transaction
{
$result = $journal->transactions->first(
static function (Transaction $transaction): bool {
return (float) $transaction->amount < 0; // lame but it works.
}
static fn (Transaction $transaction): bool => (float) $transaction->amount < 0
);
if (null === $result) {
throw new FireflyException(sprintf('Journal #%d unexpectedly has no source transaction.', $journal->id));
@@ -442,9 +440,7 @@ class TransactionGroupTransformer extends AbstractTransformer
private function getDestinationTransaction(TransactionJournal $journal): Transaction
{
$result = $journal->transactions->first(
static function (Transaction $transaction): bool {
return (float) $transaction->amount > 0; // lame but it works
}
static fn (Transaction $transaction): bool => (float) $transaction->amount > 0
);
if (null === $result) {
throw new FireflyException(sprintf('Journal #%d unexpectedly has no destination transaction.', $journal->id));

View File

@@ -24,9 +24,6 @@ declare(strict_types=1);
namespace FireflyIII;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Support\Facades\Log;
use Deprecated;
use Exception;
use FireflyIII\Enums\UserRoleEnum;
@@ -57,6 +54,8 @@ use FireflyIII\Models\UserRole;
use FireflyIII\Models\Webhook;
use FireflyIII\Notifications\Admin\UserRegistration;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\Support\Models\ReturnsIntegerIdTrait;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
@@ -66,10 +65,12 @@ use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Notifications\Notification;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Request;
use Illuminate\Support\Str;
use Laravel\Passport\HasApiTokens;
use NotificationChannels\Pushover\PushoverReceiver;
use SensitiveParameter;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
class User extends Authenticatable
@@ -77,6 +78,7 @@ class User extends Authenticatable
use HasApiTokens;
use Notifiable;
use ReturnsIntegerIdTrait;
protected $fillable = ['email', 'password', 'blocked', 'blocked_code', 'user_group_id'];
protected $hidden = ['password', 'remember_token'];
protected $table = 'users';
@@ -258,7 +260,12 @@ class User extends Authenticatable
$dbRolesIds = $dbRoles->pluck('id')->toArray();
$dbRolesTitles = $dbRoles->pluck('title')->toArray();
$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()) {
Log::error(sprintf(
'User #%d "%s" does not have roles %s in user group #%d "%s"',
@@ -370,7 +377,7 @@ class User extends Authenticatable
return match ($driver) {
'mail' => $email,
default => null,
default => null
};
}
@@ -452,7 +459,7 @@ class User extends Authenticatable
*
* @param string $token
*/
public function sendPasswordResetNotification($token): void
public function sendPasswordResetNotification(#[SensitiveParameter] $token): void
{
$ipAddress = Request::ip();
@@ -528,10 +535,6 @@ class User extends Authenticatable
protected function casts(): array
{
return [
'created_at' => 'datetime',
'updated_at' => 'datetime',
'blocked' => 'boolean',
];
return ['created_at' => 'datetime', 'updated_at' => 'datetime', 'blocked' => 'boolean'];
}
}

62
composer.lock generated
View File

@@ -1878,16 +1878,16 @@
},
{
"name": "laravel/framework",
"version": "v12.46.0",
"version": "v12.47.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/framework.git",
"reference": "9dcff48d25a632c1fadb713024c952fec489c4ae"
"reference": "ab8114c2e78f32e64eb238fc4b495bea3f8b80ec"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/9dcff48d25a632c1fadb713024c952fec489c4ae",
"reference": "9dcff48d25a632c1fadb713024c952fec489c4ae",
"url": "https://api.github.com/repos/laravel/framework/zipball/ab8114c2e78f32e64eb238fc4b495bea3f8b80ec",
"reference": "ab8114c2e78f32e64eb238fc4b495bea3f8b80ec",
"shasum": ""
},
"require": {
@@ -2096,7 +2096,7 @@
"issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework"
},
"time": "2026-01-07T23:26:53+00:00"
"time": "2026-01-13T15:29:06+00:00"
},
{
"name": "laravel/passport",
@@ -2176,16 +2176,16 @@
},
{
"name": "laravel/prompts",
"version": "v0.3.8",
"version": "v0.3.9",
"source": {
"type": "git",
"url": "https://github.com/laravel/prompts.git",
"reference": "096748cdfb81988f60090bbb839ce3205ace0d35"
"reference": "5c41bf0555b7cfefaad4e66d3046675829581ac4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/prompts/zipball/096748cdfb81988f60090bbb839ce3205ace0d35",
"reference": "096748cdfb81988f60090bbb839ce3205ace0d35",
"url": "https://api.github.com/repos/laravel/prompts/zipball/5c41bf0555b7cfefaad4e66d3046675829581ac4",
"reference": "5c41bf0555b7cfefaad4e66d3046675829581ac4",
"shasum": ""
},
"require": {
@@ -2229,22 +2229,22 @@
"description": "Add beautiful and user-friendly forms to your command-line applications.",
"support": {
"issues": "https://github.com/laravel/prompts/issues",
"source": "https://github.com/laravel/prompts/tree/v0.3.8"
"source": "https://github.com/laravel/prompts/tree/v0.3.9"
},
"time": "2025-11-21T20:52:52+00:00"
"time": "2026-01-07T21:00:29+00:00"
},
{
"name": "laravel/sanctum",
"version": "v4.2.2",
"version": "v4.2.3",
"source": {
"type": "git",
"url": "https://github.com/laravel/sanctum.git",
"reference": "fd447754d2d3f56950d53b930128af2e3b617de9"
"reference": "47d26f1d310879ff757b971f5a6fc631d18663fd"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/fd447754d2d3f56950d53b930128af2e3b617de9",
"reference": "fd447754d2d3f56950d53b930128af2e3b617de9",
"url": "https://api.github.com/repos/laravel/sanctum/zipball/47d26f1d310879ff757b971f5a6fc631d18663fd",
"reference": "47d26f1d310879ff757b971f5a6fc631d18663fd",
"shasum": ""
},
"require": {
@@ -2294,20 +2294,20 @@
"issues": "https://github.com/laravel/sanctum/issues",
"source": "https://github.com/laravel/sanctum"
},
"time": "2026-01-06T23:11:51+00:00"
"time": "2026-01-11T18:20:25+00:00"
},
{
"name": "laravel/serializable-closure",
"version": "v2.0.7",
"version": "v2.0.8",
"source": {
"type": "git",
"url": "https://github.com/laravel/serializable-closure.git",
"reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd"
"reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
"reference": "cb291e4c998ac50637c7eeb58189c14f5de5b9dd",
"url": "https://api.github.com/repos/laravel/serializable-closure/zipball/7581a4407012f5f53365e11bafc520fd7f36bc9b",
"reference": "7581a4407012f5f53365e11bafc520fd7f36bc9b",
"shasum": ""
},
"require": {
@@ -2355,7 +2355,7 @@
"issues": "https://github.com/laravel/serializable-closure/issues",
"source": "https://github.com/laravel/serializable-closure"
},
"time": "2025-11-21T20:52:36+00:00"
"time": "2026-01-08T16:22:46+00:00"
},
{
"name": "laravel/slack-notification-channel",
@@ -10081,12 +10081,12 @@
"version": "v3.16.3",
"source": {
"type": "git",
"url": "https://github.com/barryvdh/laravel-debugbar.git",
"url": "https://github.com/fruitcake/laravel-debugbar.git",
"reference": "c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e",
"url": "https://api.github.com/repos/fruitcake/laravel-debugbar/zipball/c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e",
"reference": "c91e57ea113edd6526f5b8cd6b1c6ee02c67b28e",
"shasum": ""
},
@@ -10146,8 +10146,8 @@
"webprofiler"
],
"support": {
"issues": "https://github.com/barryvdh/laravel-debugbar/issues",
"source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.16.3"
"issues": "https://github.com/fruitcake/laravel-debugbar/issues",
"source": "https://github.com/fruitcake/laravel-debugbar/tree/v3.16.3"
},
"funding": [
{
@@ -11889,16 +11889,16 @@
},
{
"name": "rector/rector",
"version": "2.3.0",
"version": "2.3.1",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
"reference": "f7166355dcf47482f27be59169b0825995f51c7d"
"reference": "9afc1bb43571b25629f353c61a9315b5ef31383a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rectorphp/rector/zipball/f7166355dcf47482f27be59169b0825995f51c7d",
"reference": "f7166355dcf47482f27be59169b0825995f51c7d",
"url": "https://api.github.com/repos/rectorphp/rector/zipball/9afc1bb43571b25629f353c61a9315b5ef31383a",
"reference": "9afc1bb43571b25629f353c61a9315b5ef31383a",
"shasum": ""
},
"require": {
@@ -11937,7 +11937,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
"source": "https://github.com/rectorphp/rector/tree/2.3.0"
"source": "https://github.com/rectorphp/rector/tree/2.3.1"
},
"funding": [
{
@@ -11945,7 +11945,7 @@
"type": "github"
}
],
"time": "2025-12-25T22:00:18+00:00"
"time": "2026-01-13T15:13:58+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -78,8 +78,8 @@ return [
'running_balance_column' => (bool)envNonEmpty('USE_RUNNING_BALANCE', true), // this is only the default value, is not used.
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-01-10',
'build_time' => 1768064227,
'version' => 'develop/2026-01-14',
'build_time' => 1768366713,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

View File

@@ -1,5 +1,8 @@
<?php
declare(strict_types=1);
/*
* WebhookDataSeeder.php

42
mago.toml Normal file
View File

@@ -0,0 +1,42 @@
# Welcome to Mago!
# For full documentation, see https://mago.carthage.software/tools/overview
php-version = "8.4.0"
[source]
workspace = "."
paths = ["app/", "database/factories/", "database/seeders/", "tests/"]
includes = ["vendor"]
excludes = []
[formatter]
print-width = 160
tab-width = 4
use-tabs = false
trailing-comma = false
method-chain-breaking-style = "same_line"
preserve-breaking-array-like = false
align-assignment-like = true
null-type-hint = "null_pipe"
[linter]
integrations = ["symfony", "laravel", "phpunit"]
[linter.rules]
ambiguous-function-call = { enabled = false }
literal-named-argument = { enabled = false }
halstead = { effort-threshold = 7000 }
prefer-early-continue = { enabled = false }
[analyzer]
find-unused-definitions = true
find-unused-expressions = true
analyze-dead-code = false
memoize-properties = true
allow-possibly-undefined-array-keys = true
check-throws = true
check-missing-override = false
find-unused-parameters = false
strict-list-index-checks = false
no-boolean-literal-comparison = false
check-missing-type-hints = false
register-super-globals = true

679
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,8 @@
* Skin: Firefly III dark
* Built upon the code below with some extra things.
* https://raw.githubusercontent.com/anvyst/adminlte-skin-midnight/master/build/less/skins/skin-midnight.less
*
* To minify, just throw it in cyber chef.
* ------------
*/
.force-background-tags-input {
@@ -163,10 +165,17 @@
.skin-firefly-iii .ti-input {
border: 1px solid #353c42 !important;
}
.skin-firefly-iii code {
background-color: #343941;
color: #c9d1d9;
.skin-firefly-iii pre {
background-color: #1f2327;
border: 1px solid #3a4046;
}
.skin-firefly-iii code {
background-color:transparent;
color:#eee;
}
.skin-firefly-iii .modal-content {
position: relative;
background-color: #353c42;

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
/*
* Skin: Blue
* Slight changes for FF3
* Slight changes for FF3.
* ----------
*/
.skin-firefly-iii ::-moz-selection {

View File

@@ -107,18 +107,18 @@
"multi_account_warning_withdrawal": "Pami\u0119taj, \u017ce konto \u017ar\u00f3d\u0142owe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wyp\u0142aty.",
"multi_account_warning_deposit": "Pami\u0119taj, \u017ce konto docelowe kolejnych podzia\u0142\u00f3w zostanie ustawione na konto zdefiniowane w pierwszym podziale wp\u0142aty.",
"multi_account_warning_transfer": "Pami\u0119taj, \u017ce konta \u017ar\u00f3d\u0142owe i docelowe kolejnych podzia\u0142\u00f3w zostan\u0105 ustawione na konto zdefiniowane w pierwszym podziale transferu.",
"webhook_trigger_ANY": "After any event",
"webhook_trigger_ANY": "Po ka\u017cdym wydarzeniu",
"webhook_trigger_STORE_TRANSACTION": "Po utworzeniu transakcji",
"webhook_trigger_UPDATE_TRANSACTION": "Po zmodyfikowaniu transakcji",
"webhook_trigger_DESTROY_TRANSACTION": "Po usuni\u0119ciu transakcji",
"webhook_trigger_STORE_BUDGET": "After budget creation",
"webhook_trigger_UPDATE_BUDGET": "After budget update",
"webhook_trigger_DESTROY_BUDGET": "After budget delete",
"webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change",
"webhook_trigger_STORE_BUDGET": "Po utworzeniu bud\u017cetu",
"webhook_trigger_UPDATE_BUDGET": "Po aktualizacji bud\u017cetu",
"webhook_trigger_DESTROY_BUDGET": "Po usuni\u0119ciu bud\u017cetu",
"webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Po zmianie kwoty bud\u017cetu",
"webhook_response_TRANSACTIONS": "Szczeg\u00f3\u0142y transakcji",
"webhook_response_RELEVANT": "Relevant details",
"webhook_response_ACCOUNTS": "Szczeg\u00f3\u0142y konta",
"webhook_response_NONE": "No details",
"webhook_response_NONE": "Brak szczeg\u00f3\u0142\u00f3w",
"webhook_delivery_JSON": "JSON",
"actions": "Akcje",
"meta_data": "Metadane",

View File

@@ -3,8 +3,8 @@
"administrations_page_title": "Administra\u00e7\u00e3o financeira",
"administrations_index_menu": "Administra\u00e7\u00e3o financeira",
"expires_at": "Expira em",
"temp_administrations_introduction": "Firefly III will soon get the ability to manage multiple financial administrations. Right now, you only have the one. You can set the title of this administration and its primary currency. This replaces the previous setting where you would set your \"default currency\". This setting is now tied to the financial administration and can be different per administration.",
"administration_currency_form_help": "It may take a long time for the page to load if you change the primary currency because transaction may need to be converted to your (new) primary currency.",
"temp_administrations_introduction": "O Firefly III ter\u00e1 em breve a capacidade de gerir m\u00faltiplas administra\u00e7\u00f5es financeiras. Neste momento, voc\u00ea tem apenas um. Voc\u00ea pode definir o t\u00edtulo desta administra\u00e7\u00e3o e sua moeda prim\u00e1ria. Isso substitui a configura\u00e7\u00e3o anterior onde voc\u00ea iria definir sua \"moeda padr\u00e3o\". Esta defini\u00e7\u00e3o est\u00e1 agora ligada \u00e0 administra\u00e7\u00e3o financeira e pode ser diferente por administra\u00e7\u00e3o.",
"administration_currency_form_help": "Pode demorar muito tempo para a p\u00e1gina carregar, se voc\u00ea alterar a moeda principal, porque a transa\u00e7\u00e3o pode precisar ser convertida para a sua (nova) moeda principal.",
"administrations_page_edit_sub_title_js": "Editar administra\u00e7\u00e3o financeira \"{title}\"",
"table": "Tabela",
"welcome_back": "O que est\u00e1 acontecendo?",
@@ -102,23 +102,23 @@
"profile_oauth_client_secret_title": "Segredo do cliente",
"profile_oauth_client_secret_expl": "Aqui est\u00e1 o seu novo segredo de cliente. Esta \u00e9 a \u00fanica vez que ela ser\u00e1 mostrada, ent\u00e3o n\u00e3o o perca! Agora voc\u00ea pode usar este segredo para fazer requisi\u00e7\u00f5es de API.",
"profile_oauth_confidential": "Confidencial",
"profile_oauth_confidential_help": "Require the client to authenticate with a secret. Confidential clients can hold credentials in a secure way without exposing them to unauthorized parties. Public applications, such as native desktop or JavaScript SPA applications, are unable to hold secrets securely.",
"profile_oauth_confidential_help": "Exigir que o cliente se autentique com um segredo. Clientes confidenciais podem segurar credenciais de forma segura sem expor a partes n\u00e3o autorizadas. Aplica\u00e7\u00f5es p\u00fablicas, como aplica\u00e7\u00f5es de \u00e1rea de trabalho nativas ou JavaScript SPA, s\u00e3o incapazes de manter segredos com seguran\u00e7a.",
"multi_account_warning_unknown": "Dependendo do tipo de transa\u00e7\u00e3o que voc\u00ea criar, a conta de origem e\/ou de destino das divis\u00f5es subsequentes pode ser sobrescrita pelo que estiver definido na primeira divis\u00e3o da transa\u00e7\u00e3o.",
"multi_account_warning_withdrawal": "Tenha em mente que a conta de origem das divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definido na primeira divis\u00e3o da sa\u00edda.",
"multi_account_warning_deposit": "Tenha em mente que a conta de destino das divis\u00f5es subsequentes ser\u00e1 sobrescrita pelo que estiver definido na primeira divis\u00e3o da entrada.",
"multi_account_warning_transfer": "Tenha em mente que a conta de origem + de destino das divis\u00f5es subsequentes ser\u00e3o sobrescritas pelo que for definido na primeira divis\u00e3o da transfer\u00eancia.",
"webhook_trigger_ANY": "After any event",
"webhook_trigger_ANY": "Ap\u00f3s qualquer evento",
"webhook_trigger_STORE_TRANSACTION": "Ap\u00f3s cria\u00e7\u00e3o da transa\u00e7\u00e3o",
"webhook_trigger_UPDATE_TRANSACTION": "Ap\u00f3s atualiza\u00e7\u00e3o da transa\u00e7\u00e3o",
"webhook_trigger_DESTROY_TRANSACTION": "Ap\u00f3s exclus\u00e3o da transa\u00e7\u00e3o",
"webhook_trigger_STORE_BUDGET": "After budget creation",
"webhook_trigger_UPDATE_BUDGET": "After budget update",
"webhook_trigger_DESTROY_BUDGET": "After budget delete",
"webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "After budgeted amount change",
"webhook_trigger_STORE_BUDGET": "Ap\u00f3s cria\u00e7\u00e3o do or\u00e7amento",
"webhook_trigger_UPDATE_BUDGET": "Ap\u00f3s atualiza\u00e7\u00e3o do or\u00e7amento",
"webhook_trigger_DESTROY_BUDGET": "Ap\u00f3s exclus\u00e3o do or\u00e7amento",
"webhook_trigger_STORE_UPDATE_BUDGET_LIMIT": "Ap\u00f3s mudan\u00e7a de valor or\u00e7ado",
"webhook_response_TRANSACTIONS": "Detalhes da transa\u00e7\u00e3o",
"webhook_response_RELEVANT": "Relevant details",
"webhook_response_RELEVANT": "Detalhes relevantes",
"webhook_response_ACCOUNTS": "Detalhes da conta",
"webhook_response_NONE": "No details",
"webhook_response_NONE": "Sem detalhes",
"webhook_delivery_JSON": "JSON",
"actions": "A\u00e7\u00f5es",
"meta_data": "Meta dados",
@@ -160,7 +160,7 @@
"url": "URL",
"active": "Ativo",
"interest_date": "Data do juros",
"administration_currency": "Primary currency",
"administration_currency": "Moeda principal",
"title": "T\u00edtulo",
"date": "Data",
"book_date": "Data de lan\u00e7amento",
@@ -180,7 +180,7 @@
"list": {
"title": "T\u00edtulo",
"active": "Est\u00e1 ativo?",
"primary_currency": "Primary currency",
"primary_currency": "Moeda principal",
"trigger": "Gatilho",
"response": "Resposta",
"delivery": "Entrega",

View File

@@ -30,8 +30,8 @@
"submission_options": "Op\u0163iuni de depunere",
"apply_rules_checkbox": "Aplic\u0103 regulile",
"fire_webhooks_checkbox": "Webhook-uri de incendiu",
"no_budget_pointer": "Se pare c\u0103 nu ave\u021bi \u00eenc\u0103 bugete. Ar trebui s\u0103 crea\u021bi c\u00e2teva pe pagina <a href=\"\/budgets\">bugete<\/a>. Bugetele v\u0103 pot ajuta s\u0103 \u021bine\u021bi eviden\u021ba cheltuielilor.",
"no_bill_pointer": "You seem to have no subscription yet. You should create some on the <a href=\"subscriptions\">subscription<\/a>-page. Subscriptions can help you keep track of expenses.",
"no_budget_pointer": "Se pare c\u0103 nu ai \u00eenc\u0103 bugete. Creeaz\u0103-le pe pagina <a href=\"budgets\">bugete<\/a>. Bugetele te ajut\u0103 s\u0103 \u021bii eviden\u021ba cheltuielilor.",
"no_bill_pointer": "Se pare c\u0103 nu ai \u00eenc\u0103 abonamente. Creeaz\u0103-le pe pagina <a href=\"subscriptions\">abonamente<\/a>. Abonamentele te ajut\u0103 s\u0103 \u021bii eviden\u021ba cheltuielilor.",
"source_account": "Contul surs\u0103",
"hidden_fields_preferences": "Pute\u021bi activa mai multe op\u021biuni de tranzac\u021bie \u00een <a href=\"preferences\">preferin\u021bele<\/a> dvs.",
"destination_account": "Contul de destina\u021bie",
@@ -189,6 +189,6 @@
},
"config": {
"html_language": "ro",
"date_time_fns": "MMMM do yyy @ HH:mm:ss"
"date_time_fns": "MMMM do yyyy @ HH:mm:ss"
}
}