diff --git a/.ci/php-cs-fixer/composer.lock b/.ci/php-cs-fixer/composer.lock index 4e1d937036..4f9ef616a0 100644 --- a/.ci/php-cs-fixer/composer.lock +++ b/.ci/php-cs-fixer/composer.lock @@ -402,16 +402,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.88.2", + "version": "v3.89.1", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99" + "reference": "f34967da2866ace090a2b447de1f357356474573" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a8d15584bafb0f0d9d938827840060fd4a3ebc99", - "reference": "a8d15584bafb0f0d9d938827840060fd4a3ebc99", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/f34967da2866ace090a2b447de1f357356474573", + "reference": "f34967da2866ace090a2b447de1f357356474573", "shasum": "" }, "require": { @@ -426,7 +426,6 @@ "php": "^7.4 || ^8.0", "react/child-process": "^0.6.6", "react/event-loop": "^1.5", - "react/promise": "^3.3", "react/socket": "^1.16", "react/stream": "^1.4", "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0", @@ -494,7 +493,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.88.2" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.89.1" }, "funding": [ { @@ -502,7 +501,7 @@ "type": "github" } ], - "time": "2025-09-27T00:24:15+00:00" + "time": "2025-10-24T12:05:10+00:00" }, { "name": "psr/container", @@ -1252,16 +1251,16 @@ }, { "name": "symfony/console", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7", + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7", "shasum": "" }, "require": { @@ -1326,7 +1325,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.4" + "source": "https://github.com/symfony/console/tree/v7.3.5" }, "funding": [ { @@ -1346,7 +1345,7 @@ "type": "tidelift" } ], - "time": "2025-09-22T15:31:00+00:00" + "time": "2025-10-14T15:46:26+00:00" }, { "name": "symfony/deprecation-contracts", @@ -1647,16 +1646,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -1691,7 +1690,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -1711,7 +1710,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/options-resolver", diff --git a/THANKS.md b/THANKS.md index 2839b6f7f6..4b590fc92b 100755 --- a/THANKS.md +++ b/THANKS.md @@ -4,6 +4,8 @@ Over time, many people have contributed to Firefly III. Their efforts are not al Please find below all the people who contributed to the Firefly III code. Their names are mentioned in the year of their first contribution. ## 2025 +- jreyesr +- codearena-bot - Nicky De Maeyer - Denis Iskandarov - Lompi diff --git a/app/Api/V1/Controllers/Autocomplete/AccountController.php b/app/Api/V1/Controllers/Autocomplete/AccountController.php index c3f7cd9618..dfb16b0eef 100644 --- a/app/Api/V1/Controllers/Autocomplete/AccountController.php +++ b/app/Api/V1/Controllers/Autocomplete/AccountController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Exceptions\FireflyException; @@ -79,20 +79,26 @@ class AccountController extends Controller * @throws FireflyException * @throws FireflyException */ - public function accounts(AutocompleteRequest $request): JsonResponse + public function accounts(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $types = $data['types']; - $query = $data['query']; - $date = $data['date'] ?? today(config('app.timezone')); - $return = []; - $timer = Timer::getInstance(); - $timer->start(sprintf('AC accounts "%s"', $query)); - $result = $this->repository->searchAccount((string) $query, $types, $this->parameters->get('limit')); + [ + '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(); + $timer->start(sprintf('AC accounts "%s"', $query)); + $result = $this->repository->searchAccount((string)$query, $types, $limit); $allBalances = Steam::accountsBalancesOptimized($result, $date, $this->primaryCurrency, $this->convertToPrimary); /** @var Account $account */ @@ -111,17 +117,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, @@ -134,8 +140,8 @@ class AccountController extends Controller $return, static function (array $left, array $right) { $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); + $posA = (int)array_search($left['type'], $order, true); + $posB = (int)array_search($right['type'], $order, true); return $posA - $posB; } diff --git a/app/Api/V1/Controllers/Autocomplete/BillController.php b/app/Api/V1/Controllers/Autocomplete/BillController.php index 5fea014b5c..4c0da6fc4c 100644 --- a/app/Api/V1/Controllers/Autocomplete/BillController.php +++ b/app/Api/V1/Controllers/Autocomplete/BillController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Bill; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -61,10 +61,9 @@ class BillController extends Controller * Documentation for this endpoint is at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getBillsAC */ - public function bills(AutocompleteRequest $request): JsonResponse + public function bills(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $result = $this->repository->searchBill($data['query'], $this->parameters->get('limit')); + $result = $this->repository->searchBill($request->attributes->get('query'), $request->attributes->get('limit')); $filtered = $result->map( static fn (Bill $item) => [ 'id' => (string) $item->id, diff --git a/app/Api/V1/Controllers/Autocomplete/BudgetController.php b/app/Api/V1/Controllers/Autocomplete/BudgetController.php index c292adc5b1..4a096bff1e 100644 --- a/app/Api/V1/Controllers/Autocomplete/BudgetController.php +++ b/app/Api/V1/Controllers/Autocomplete/BudgetController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Budget; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -61,10 +61,9 @@ class BudgetController extends Controller * Documentation for this endpoint is at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getBudgetsAC */ - public function budgets(AutocompleteRequest $request): JsonResponse + public function budgets(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $result = $this->repository->searchBudget($data['query'], $this->parameters->get('limit')); + $result = $this->repository->searchBudget($request->attributes->get('query'), $request->attributes->get('limit')); $filtered = $result->map( static fn (Budget $item) => [ 'id' => (string) $item->id, diff --git a/app/Api/V1/Controllers/Autocomplete/CategoryController.php b/app/Api/V1/Controllers/Autocomplete/CategoryController.php index cf24f56776..34a963b5ea 100644 --- a/app/Api/V1/Controllers/Autocomplete/CategoryController.php +++ b/app/Api/V1/Controllers/Autocomplete/CategoryController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -61,10 +61,9 @@ class CategoryController extends Controller * Documentation for this endpoint is at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getCategoriesAC */ - public function categories(AutocompleteRequest $request): JsonResponse + public function categories(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $result = $this->repository->searchCategory($data['query'], $this->parameters->get('limit')); + $result = $this->repository->searchCategory($request->attributes->get('query'), $request->attributes->get('limit')); $filtered = $result->map( static fn (Category $item) => [ 'id' => (string) $item->id, diff --git a/app/Api/V1/Controllers/Autocomplete/CurrencyController.php b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php index 427e03d5b9..1528e17c18 100644 --- a/app/Api/V1/Controllers/Autocomplete/CurrencyController.php +++ b/app/Api/V1/Controllers/Autocomplete/CurrencyController.php @@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Autocomplete; use Deprecated; use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\TransactionCurrency; @@ -62,10 +63,9 @@ class CurrencyController extends Controller * Documentation for this endpoint is at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getCurrenciesAC */ - public function currencies(AutocompleteRequest $request): JsonResponse + public function currencies(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $collection = $this->repository->searchCurrency($data['query'], $this->parameters->get('limit')); + $collection = $this->repository->searchCurrency($request->attributes->get('query'), $request->attributes->get('limit')); $result = []; /** @var TransactionCurrency $currency */ diff --git a/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php index 0f4126f941..38a2ce56be 100644 --- a/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/ObjectGroupController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\ObjectGroup; use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface; @@ -61,11 +61,10 @@ class ObjectGroupController extends Controller * Documentation for this endpoint is at: * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/autocomplete/getObjectGroupsAC */ - public function objectGroups(AutocompleteRequest $request): JsonResponse + public function objectGroups(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); $return = []; - $result = $this->repository->search($data['query'], $this->parameters->get('limit')); + $result = $this->repository->search($request->attributes->get('query'), $request->attributes->get('limit')); /** @var ObjectGroup $objectGroup */ foreach ($result as $objectGroup) { diff --git a/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php index 25fc1f145e..f31a990cf0 100644 --- a/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php +++ b/app/Api/V1/Controllers/Autocomplete/PiggyBankController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\TransactionCurrency; @@ -64,10 +64,9 @@ class PiggyBankController extends Controller ); } - public function piggyBanks(AutocompleteRequest $request): JsonResponse + public function piggyBanks(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit')); + $piggies = $this->piggyRepository->searchPiggyBank($request->attributes->get('query'), $request->attributes->get('limit')); $response = []; /** @var PiggyBank $piggy */ @@ -90,10 +89,9 @@ class PiggyBankController extends Controller return response()->api($response); } - public function piggyBanksWithBalance(AutocompleteRequest $request): JsonResponse + public function piggyBanksWithBalance(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $piggies = $this->piggyRepository->searchPiggyBank($data['query'], $this->parameters->get('limit')); + $piggies = $this->piggyRepository->searchPiggyBank($request->attributes->get('query'), $request->attributes->get('limit')); $response = []; /** @var PiggyBank $piggy */ diff --git a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php index 202cf398f2..e757d098c1 100644 --- a/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php +++ b/app/Api/V1/Controllers/Autocomplete/RecurrenceController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Recurrence; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; @@ -57,10 +57,9 @@ class RecurrenceController extends Controller ); } - public function recurring(AutocompleteRequest $request): JsonResponse + public function recurring(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $recurrences = $this->repository->searchRecurrence($data['query'], $this->parameters->get('limit')); + $recurrences = $this->repository->searchRecurrence($request->attributes->get('query'), $request->attributes->get('limit')); $response = []; /** @var Recurrence $recurrence */ diff --git a/app/Api/V1/Controllers/Autocomplete/RuleController.php b/app/Api/V1/Controllers/Autocomplete/RuleController.php index c2de7bb83b..1ff0271607 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Rule; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; @@ -57,10 +57,9 @@ class RuleController extends Controller ); } - public function rules(AutocompleteRequest $request): JsonResponse + public function rules(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $rules = $this->repository->searchRule($data['query'], $this->parameters->get('limit')); + $rules = $this->repository->searchRule($request->attributes->get('query'), $request->attributes->get('limit')); $response = []; /** @var Rule $rule */ diff --git a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php index ff496d1e13..70c66be5ee 100644 --- a/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php +++ b/app/Api/V1/Controllers/Autocomplete/RuleGroupController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; @@ -57,10 +57,9 @@ class RuleGroupController extends Controller ); } - public function ruleGroups(AutocompleteRequest $request): JsonResponse + public function ruleGroups(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $groups = $this->repository->searchRuleGroup($data['query'], $this->parameters->get('limit')); + $groups = $this->repository->searchRuleGroup($request->attributes->get('query'), $request->attributes->get('limit')); $response = []; /** @var RuleGroup $group */ diff --git a/app/Api/V1/Controllers/Autocomplete/TagController.php b/app/Api/V1/Controllers/Autocomplete/TagController.php index 5ad94ea7f5..d797931319 100644 --- a/app/Api/V1/Controllers/Autocomplete/TagController.php +++ b/app/Api/V1/Controllers/Autocomplete/TagController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Tag\TagRepositoryInterface; @@ -57,10 +57,9 @@ class TagController extends Controller ); } - public function tags(AutocompleteRequest $request): JsonResponse + public function tags(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $result = $this->repository->searchTags($data['query'], $this->parameters->get('limit')); + $result = $this->repository->searchTags($request->attributes->get('query'), $request->attributes->get('limit')); $array = []; /** @var Tag $tag */ diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionController.php b/app/Api/V1/Controllers/Autocomplete/TransactionController.php index d088427937..2bc5c8314b 100644 --- a/app/Api/V1/Controllers/Autocomplete/TransactionController.php +++ b/app/Api/V1/Controllers/Autocomplete/TransactionController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; @@ -64,10 +64,9 @@ class TransactionController extends Controller ); } - public function transactions(AutocompleteRequest $request): JsonResponse + public function transactions(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $result = $this->repository->searchJournalDescriptions($data['query'], $this->parameters->get('limit')); + $result = $this->repository->searchJournalDescriptions($request->attributes->get('query'), $request->attributes->get('limit')); // limit and unique $filtered = $result->unique('description'); @@ -86,13 +85,12 @@ class TransactionController extends Controller return response()->api($array); } - public function transactionsWithID(AutocompleteRequest $request): JsonResponse + public function transactionsWithID(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); $result = new Collection(); - if (is_numeric($data['query'])) { + if (is_numeric($request->attributes->get('query'))) { // search for group, not journal. - $firstResult = $this->groupRepository->find((int) $data['query']); + $firstResult = $this->groupRepository->find((int) $request->attributes->get('query')); if ($firstResult instanceof TransactionGroup) { // group may contain multiple journals, each a result: foreach ($firstResult->transactionJournals as $journal) { @@ -100,8 +98,8 @@ class TransactionController extends Controller } } } - if (!is_numeric($data['query'])) { - $result = $this->repository->searchJournalDescriptions($data['query'], $this->parameters->get('limit')); + if (!is_numeric($request->attributes->get('query'))) { + $result = $this->repository->searchJournalDescriptions($request->attributes->get('query'), $request->attributes->get('limit')); } // limit and unique diff --git a/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php index 357efc37a1..bdfad0d9d9 100644 --- a/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php +++ b/app/Api/V1/Controllers/Autocomplete/TransactionTypeController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Autocomplete; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteRequest; +use FireflyIII\Api\V1\Requests\Autocomplete\AutocompleteApiRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; @@ -55,10 +55,9 @@ class TransactionTypeController extends Controller ); } - public function transactionTypes(AutocompleteRequest $request): JsonResponse + public function transactionTypes(AutocompleteApiRequest $request): JsonResponse { - $data = $request->getData(); - $types = $this->repository->searchTypes($data['query'], $this->parameters->get('limit')); + $types = $this->repository->searchTypes($request->attributes->get('query'), $request->attributes->get('limit')); $array = []; /** @var TransactionType $type */ diff --git a/app/Api/V1/Controllers/Chart/BudgetController.php b/app/Api/V1/Controllers/Chart/BudgetController.php index 6f0425d6fb..090b90f65a 100644 --- a/app/Api/V1/Controllers/Chart/BudgetController.php +++ b/app/Api/V1/Controllers/Chart/BudgetController.php @@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Data\SameDateRequest; +use FireflyIII\Api\V1\Requests\DateRangeRequest; use FireflyIII\Enums\UserRoleEnum; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Budget; @@ -83,15 +83,13 @@ class BudgetController extends Controller * * @throws FireflyException */ - public function overview(SameDateRequest $request): JsonResponse + public function overview(DateRangeRequest $request): JsonResponse { - $params = $request->getAll(); - /** @var Carbon $start */ - $start = $params['start']; + $start = $request->attributes->get('start'); /** @var Carbon $end */ - $end = $params['end']; + $end = $request->attributes->get('end'); // code from FrontpageChartGenerator, but not in separate class $budgets = $this->repository->getActiveBudgets(); diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php index a81261e3ea..a85fc89fa3 100644 --- a/app/Api/V1/Controllers/Chart/CategoryController.php +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Data\SameDateRequest; +use FireflyIII\Api\V1\Requests\DateRangeRequest; use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Enums\UserRoleEnum; @@ -80,13 +80,13 @@ class CategoryController extends Controller * * @SuppressWarnings("PHPMD.UnusedFormalParameter") */ - public function overview(SameDateRequest $request): JsonResponse + public function overview(DateRangeRequest $request): JsonResponse { /** @var Carbon $start */ - $start = $this->parameters->get('start'); + $start = $request->attributes->get('start'); /** @var Carbon $end */ - $end = $this->parameters->get('end'); + $end = $request->attributes->get('end'); $accounts = $this->accountRepos->getAccountsByType([AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::ASSET->value]); $currencies = []; $return = []; diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 298bc20cc7..c9169673b5 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -29,6 +29,7 @@ use Carbon\Exceptions\InvalidFormatException; use FireflyIII\Exceptions\BadHttpHeaderException; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Support\Facades\Amount; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Http\Api\ValidatesUserGroupTrait; use FireflyIII\Transformers\AbstractTransformer; @@ -67,6 +68,8 @@ abstract class Controller extends BaseController protected bool $convertToPrimary = false; protected TransactionCurrency $primaryCurrency; + + /** @deprecated use Request classes */ protected ParameterBag $parameters; /** @@ -98,7 +101,8 @@ abstract class Controller extends BaseController } /** - * Method to grab all parameters from the URL. + * @deprecated use Request classes + * Method to grab all parameters from the URL */ private function getParameters(): ParameterBag { @@ -156,7 +160,7 @@ abstract class Controller extends BaseController /** @var User $user */ $user = auth()->user(); - $pageSize = (int)app('preferences')->getForUser($user, 'listPageSize', 50)->data; + $pageSize = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; $bag->set($integer, $pageSize); } } diff --git a/app/Api/V1/Controllers/Data/DestroyController.php b/app/Api/V1/Controllers/Data/DestroyController.php index 87e791be7c..86180cf44c 100644 --- a/app/Api/V1/Controllers/Data/DestroyController.php +++ b/app/Api/V1/Controllers/Data/DestroyController.php @@ -46,6 +46,7 @@ use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Services\Internal\Destroy\AccountDestroyService; use FireflyIII\Services\Internal\Destroy\JournalDestroyService; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; @@ -102,7 +103,7 @@ class DestroyController extends Controller default => throw new FireflyException(sprintf('200033: This endpoint can\'t handle object "%s"', $objects)), }; - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Account/DestroyController.php b/app/Api/V1/Controllers/Models/Account/DestroyController.php index fc23429f76..d772b44557 100644 --- a/app/Api/V1/Controllers/Models/Account/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Account/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Account; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -60,7 +61,7 @@ class DestroyController extends Controller public function destroy(Account $account): JsonResponse { $this->repository->destroy($account, null); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Account/ListController.php b/app/Api/V1/Controllers/Models/Account/ListController.php index b230572b93..f830be0844 100644 --- a/app/Api/V1/Controllers/Models/Account/ListController.php +++ b/app/Api/V1/Controllers/Models/Account/ListController.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Models\Account; use FireflyIII\Api\V1\Controllers\Controller; +use FireflyIII\Api\V1\Requests\PaginationRequest; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -69,22 +70,25 @@ class ListController extends Controller ); } - public function attachments(Account $account): JsonResponse + public function attachments(PaginationRequest $request, Account $account): JsonResponse { $manager = $this->getManager(); - $pageSize = $this->parameters->get('limit'); + [ + 'limit' => $limit, + 'offset' => $offset, + 'page' => $page, + ] = $request->attributes->all(); $collection = $this->repository->getAttachments($account); $count = $collection->count(); - $attachments = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + $attachments = $collection->slice($offset, $limit); // make paginator: - $paginator = new LengthAwarePaginator($attachments, $count, $pageSize, $this->parameters->get('page')); + $paginator = new LengthAwarePaginator($attachments, $count, $limit, $page); $paginator->setPath(route('api.v1.accounts.attachments', [$account->id]).$this->buildParams()); /** @var AttachmentTransformer $transformer */ $transformer = app(AttachmentTransformer::class); - $transformer->setParameters($this->parameters); $resource = new FractalCollection($attachments, $transformer, 'attachments'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); @@ -92,18 +96,21 @@ class ListController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } - public function piggyBanks(Account $account): JsonResponse + public function piggyBanks(PaginationRequest $request, Account $account): JsonResponse { // create some objects: $manager = $this->getManager(); - // types to get, page size: - $pageSize = $this->parameters->get('limit'); + [ + 'limit' => $limit, + 'offset' => $offset, + 'page' => $page, + ] = $request->attributes->all(); // get list of piggy banks. Count it and split it. $collection = $this->repository->getPiggyBanks($account); $count = $collection->count(); - $piggyBanks = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); + $piggyBanks = $collection->slice(($page - 1) * $limit, $limit); // enrich /** @var User $admin */ @@ -113,12 +120,12 @@ class ListController extends Controller $piggyBanks = $enrichment->enrich($piggyBanks); // make paginator: - $paginator = new LengthAwarePaginator($piggyBanks, $count, $pageSize, $this->parameters->get('page')); + $paginator = new LengthAwarePaginator($piggyBanks, $count, $limit, $page); $paginator->setPath(route('api.v1.accounts.piggy-banks', [$account->id]).$this->buildParams()); /** @var PiggyBankTransformer $transformer */ $transformer = app(PiggyBankTransformer::class); - $transformer->setParameters($this->parameters); + // $transformer->setParameters($this->parameters); $resource = new FractalCollection($piggyBanks, $transformer, 'piggy-banks'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); diff --git a/app/Api/V1/Controllers/Models/Account/ShowController.php b/app/Api/V1/Controllers/Models/Account/ShowController.php index 587076d0db..5616869a8d 100644 --- a/app/Api/V1/Controllers/Models/Account/ShowController.php +++ b/app/Api/V1/Controllers/Models/Account/ShowController.php @@ -71,45 +71,45 @@ class ShowController extends Controller public function index(ShowRequest $request): JsonResponse { $manager = $this->getManager(); - $params = $request->getParameters(); - $this->parameters->set('type', $params['type']); - - // types to get, page size: - $types = $this->mapAccountTypes($params['type']); + [ + 'types' => $types, + 'page' => $page, + 'limit' => $limit, + 'offset' => $offset, + 'sort' => $sort, + 'start' => $start, + 'end' => $end, + 'date' => $date, + ] + = $request->attributes->all(); // get list of accounts. Count it and split it. $this->repository->resetAccountOrder(); - $collection = $this->repository->getAccountsByType($types, $params['sort']); + $collection = $this->repository->getAccountsByType($types, $sort); $count = $collection->count(); // continue sort: // TODO if the user sorts on DB dependent field there must be no slice before enrichment, only after. // TODO still need to figure out how to do this easily. - $accounts = $collection->slice(($this->parameters->get('page') - 1) * $params['limit'], $params['limit']); - - // #11007 go to the end of the previous day. - $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); - // #11018 also end of the day. - $this->parameters->set('end', $this->parameters->get('end')?->endOfDay()); + $accounts = $collection->slice($offset, $limit); // enrich /** @var User $admin */ $admin = auth()->user(); $enrichment = new AccountEnrichment(); - $enrichment->setSort($params['sort']); - $enrichment->setDate($this->parameters->get('date')); - $enrichment->setStart($this->parameters->get('start')); - $enrichment->setEnd($this->parameters->get('end')); + $enrichment->setSort($sort); + $enrichment->setDate($date); + $enrichment->setStart($start); + $enrichment->setEnd($end); $enrichment->setUser($admin); $accounts = $enrichment->enrich($accounts); // make paginator: - $paginator = new LengthAwarePaginator($accounts, $count, $params['limit'], $this->parameters->get('page')); + $paginator = new LengthAwarePaginator($accounts, $count, $limit, $page); $paginator->setPath(route('api.v1.accounts.index').$this->buildParams()); /** @var AccountTransformer $transformer */ $transformer = app(AccountTransformer::class); - $transformer->setParameters($this->parameters); $resource = new FractalCollection($accounts, $transformer, self::RESOURCE_KEY); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); @@ -128,28 +128,25 @@ class ShowController extends Controller // get list of accounts. Count it and split it. $this->repository->resetAccountOrder(); $account->refresh(); - $manager = $this->getManager(); - - // #11007 go to the end of the previous day. - $this->parameters->set('start', $this->parameters->get('start')?->subSecond()); - // #11018 also end of the day. - $this->parameters->set('end', $this->parameters->get('end')?->endOfDay()); + $manager = $this->getManager(); + ['start' => $start, + 'end' => $end, + 'date' => $date,] = $request->attributes->all(); // enrich /** @var User $admin */ - $admin = auth()->user(); - $enrichment = new AccountEnrichment(); - $enrichment->setDate($this->parameters->get('date')); - $enrichment->setStart($this->parameters->get('start')); - $enrichment->setEnd($this->parameters->get('end')); + $admin = auth()->user(); + $enrichment = new AccountEnrichment(); + $enrichment->setDate($date); + $enrichment->setStart($start); + $enrichment->setEnd($end); $enrichment->setUser($admin); - $account = $enrichment->enrichSingle($account); + $account = $enrichment->enrichSingle($account); /** @var AccountTransformer $transformer */ - $transformer = app(AccountTransformer::class); - $transformer->setParameters($this->parameters); - $resource = new Item($account, $transformer, self::RESOURCE_KEY); + $transformer = app(AccountTransformer::class); + $resource = new Item($account, $transformer, self::RESOURCE_KEY); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE); } diff --git a/app/Api/V1/Controllers/Models/Account/UpdateController.php b/app/Api/V1/Controllers/Models/Account/UpdateController.php index 5118b1116d..5ae8f5b45d 100644 --- a/app/Api/V1/Controllers/Models/Account/UpdateController.php +++ b/app/Api/V1/Controllers/Models/Account/UpdateController.php @@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\Models\Account\UpdateRequest; use FireflyIII\Models\Account; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\JsonApi\Enrichments\AccountEnrichment; use FireflyIII\Transformers\AccountTransformer; use FireflyIII\User; @@ -74,7 +75,7 @@ class UpdateController extends Controller $account = $this->repository->update($account, $data); $manager = $this->getManager(); $account->refresh(); - app('preferences')->mark(); + Preferences::mark(); // enrich /** @var User $admin */ diff --git a/app/Api/V1/Controllers/Models/Attachment/DestroyController.php b/app/Api/V1/Controllers/Models/Attachment/DestroyController.php index c7ee0d20e5..09cfbe1557 100644 --- a/app/Api/V1/Controllers/Models/Attachment/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Attachment/DestroyController.php @@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Middleware\ApiDemoUser; use FireflyIII\Models\Attachment; use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; @@ -74,7 +75,7 @@ class DestroyController extends Controller } $this->repository->destroy($attachment); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Bill/DestroyController.php b/app/Api/V1/Controllers/Models/Bill/DestroyController.php index d62697d950..a709c73ec2 100644 --- a/app/Api/V1/Controllers/Models/Bill/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Bill/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Bill; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Bill; use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -61,7 +62,7 @@ class DestroyController extends Controller public function destroy(Bill $bill): JsonResponse { $this->repository->destroy($bill); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Budget/DestroyController.php b/app/Api/V1/Controllers/Models/Budget/DestroyController.php index 93e2f738ec..05ee1da9e8 100644 --- a/app/Api/V1/Controllers/Models/Budget/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Budget/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Budget; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -61,7 +62,7 @@ class DestroyController extends Controller public function destroy(Budget $budget): JsonResponse { $this->repository->destroy($budget); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/BudgetLimit/DestroyController.php b/app/Api/V1/Controllers/Models/BudgetLimit/DestroyController.php index 7c037dafd1..af96515cfb 100644 --- a/app/Api/V1/Controllers/Models/BudgetLimit/DestroyController.php +++ b/app/Api/V1/Controllers/Models/BudgetLimit/DestroyController.php @@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -71,7 +72,7 @@ class DestroyController extends Controller throw new FireflyException('20028: The budget limit does not belong to the budget.'); } $this->blRepository->destroyBudgetLimit($budgetLimit); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php b/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php index 038c24a46c..501d680599 100644 --- a/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php +++ b/app/Api/V1/Controllers/Models/BudgetLimit/ShowController.php @@ -172,7 +172,6 @@ class ShowController extends Controller /** @var BudgetLimitTransformer $transformer */ $transformer = app(BudgetLimitTransformer::class); - $transformer->setParameters($this->parameters); $resource = new Item($budgetLimit, $transformer, 'budget_limits'); diff --git a/app/Api/V1/Controllers/Models/Category/DestroyController.php b/app/Api/V1/Controllers/Models/Category/DestroyController.php index 29c58b2015..7d7295596f 100644 --- a/app/Api/V1/Controllers/Models/Category/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Category/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Category; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -61,7 +62,7 @@ class DestroyController extends Controller public function destroy(Category $category): JsonResponse { $this->repository->destroy($category); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php index 733517a506..27ce2e6330 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/DestroyController.php @@ -28,6 +28,7 @@ use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\Models\CurrencyExchangeRate\DestroyRequest; use FireflyIII\Enums\UserRoleEnum; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\ExchangeRate\ExchangeRateRepositoryInterface; @@ -75,6 +76,9 @@ class DestroyController extends Controller if ($exchangeRate instanceof CurrencyExchangeRate) { $this->repository->deleteRate($exchangeRate); } + if (!$exchangeRate instanceof CurrencyExchangeRate) { + throw new FireflyException('Bla'); + } return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php index 8326f44c52..19a34d1b5e 100644 --- a/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php +++ b/app/Api/V1/Controllers/Models/CurrencyExchangeRate/UpdateController.php @@ -82,7 +82,6 @@ class UpdateController extends Controller $exchangeRate = $this->repository->updateExchangeRate($exchangeRate, $rate, $date); $transformer = new ExchangeRateTransformer(); - $transformer->setParameters($this->parameters); return response() ->api($this->jsonApiObject(self::RESOURCE_KEY, $exchangeRate, $transformer)) diff --git a/app/Api/V1/Controllers/Models/ObjectGroup/DestroyController.php b/app/Api/V1/Controllers/Models/ObjectGroup/DestroyController.php index 02c229057a..097abb404b 100644 --- a/app/Api/V1/Controllers/Models/ObjectGroup/DestroyController.php +++ b/app/Api/V1/Controllers/Models/ObjectGroup/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\ObjectGroup; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\ObjectGroup; use FireflyIII\Repositories\ObjectGroup\ObjectGroupRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -64,7 +65,7 @@ class DestroyController extends Controller public function destroy(ObjectGroup $objectGroup): JsonResponse { $this->repository->destroy($objectGroup); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/PiggyBank/DestroyController.php b/app/Api/V1/Controllers/Models/PiggyBank/DestroyController.php index 5b5736e767..ce77892d37 100644 --- a/app/Api/V1/Controllers/Models/PiggyBank/DestroyController.php +++ b/app/Api/V1/Controllers/Models/PiggyBank/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\PiggyBank; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\PiggyBank; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -61,7 +62,7 @@ class DestroyController extends Controller public function destroy(PiggyBank $piggyBank): JsonResponse { $this->repository->destroy($piggyBank); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Recurrence/DestroyController.php b/app/Api/V1/Controllers/Models/Recurrence/DestroyController.php index fa67c50147..8d596798d9 100644 --- a/app/Api/V1/Controllers/Models/Recurrence/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Recurrence/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Recurrence; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Recurrence; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; /** @@ -61,7 +62,7 @@ class DestroyController extends Controller public function destroy(Recurrence $recurrence): JsonResponse { $this->repository->destroy($recurrence); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Rule/DestroyController.php b/app/Api/V1/Controllers/Models/Rule/DestroyController.php index 2144a5e34f..e8a9dbd124 100644 --- a/app/Api/V1/Controllers/Models/Rule/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Rule/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Rule; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Rule; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -65,7 +66,7 @@ class DestroyController extends Controller public function destroy(Rule $rule): JsonResponse { $this->ruleRepository->destroy($rule); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/RuleGroup/DestroyController.php b/app/Api/V1/Controllers/Models/RuleGroup/DestroyController.php index e5ac3ccf71..9924ba1c75 100644 --- a/app/Api/V1/Controllers/Models/RuleGroup/DestroyController.php +++ b/app/Api/V1/Controllers/Models/RuleGroup/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\RuleGroup; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -65,7 +66,7 @@ class DestroyController extends Controller public function destroy(RuleGroup $ruleGroup): JsonResponse { $this->ruleGroupRepository->destroy($ruleGroup, null); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Tag/DestroyController.php b/app/Api/V1/Controllers/Models/Tag/DestroyController.php index 2f46224b32..fa6a31071a 100644 --- a/app/Api/V1/Controllers/Models/Tag/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Tag/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\Tag; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -65,7 +66,7 @@ class DestroyController extends Controller public function destroy(Tag $tag): JsonResponse { $this->repository->destroy($tag); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Transaction/DestroyController.php b/app/Api/V1/Controllers/Models/Transaction/DestroyController.php index f77339b188..ecf6a28432 100644 --- a/app/Api/V1/Controllers/Models/Transaction/DestroyController.php +++ b/app/Api/V1/Controllers/Models/Transaction/DestroyController.php @@ -32,6 +32,7 @@ use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepository; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; @@ -92,7 +93,7 @@ class DestroyController extends Controller $this->groupRepository->destroy($transactionGroup); - app('preferences')->mark(); + Preferences::mark(); /** @var Account $account */ foreach ($accounts as $account) { @@ -112,7 +113,7 @@ class DestroyController extends Controller public function destroyJournal(TransactionJournal $transactionJournal): JsonResponse { $this->repository->destroyJournal($transactionJournal); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/Transaction/StoreController.php b/app/Api/V1/Controllers/Models/Transaction/StoreController.php index 37acce2eb6..6a7e603d5d 100644 --- a/app/Api/V1/Controllers/Models/Transaction/StoreController.php +++ b/app/Api/V1/Controllers/Models/Transaction/StoreController.php @@ -33,6 +33,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Rules\IsDuplicateTransaction; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment; use FireflyIII\Transformers\TransactionGroupTransformer; @@ -107,7 +108,7 @@ class StoreController extends Controller throw new ValidationException($validator); } - app('preferences')->mark(); + Preferences::mark(); $applyRules = $data['apply_rules'] ?? true; $fireWebhooks = $data['fire_webhooks'] ?? true; event(new StoredTransactionGroup($transactionGroup, $applyRules, $fireWebhooks)); diff --git a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php index 6db7503671..02fbb9b023 100644 --- a/app/Api/V1/Controllers/Models/Transaction/UpdateController.php +++ b/app/Api/V1/Controllers/Models/Transaction/UpdateController.php @@ -30,6 +30,7 @@ use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionGroup; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\JsonApi\Enrichments\TransactionGroupEnrichment; use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; @@ -79,7 +80,7 @@ class UpdateController extends Controller $newHash = $this->groupRepository->getCompareHash($transactionGroup); $manager = $this->getManager(); - app('preferences')->mark(); + Preferences::mark(); $applyRules = $data['apply_rules'] ?? true; $fireWebhooks = $data['fire_webhooks'] ?? true; $runRecalculations = $oldHash !== $newHash; diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/DestroyController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/DestroyController.php index b4aca8b763..a40d05dd2f 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/DestroyController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/DestroyController.php @@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Validator; @@ -89,7 +90,7 @@ class DestroyController extends Controller } $this->repository->destroy($currency); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php index 56b61b9c99..b225556b54 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/StoreController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\TransactionCurrency; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\Models\TransactionCurrency\StoreRequest; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CurrencyTransformer; @@ -71,7 +72,7 @@ class StoreController extends Controller $currency = $this->repository->store($request->getAll()); if (true === $request->boolean('default')) { $this->repository->makePrimary($currency); - app('preferences')->mark(); + Preferences::mark(); } $manager = $this->getManager(); diff --git a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php index f76bfaf8a2..ca8b3f14da 100644 --- a/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php +++ b/app/Api/V1/Controllers/Models/TransactionCurrency/UpdateController.php @@ -29,6 +29,7 @@ use FireflyIII\Api\V1\Requests\Models\TransactionCurrency\UpdateRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CurrencyTransformer; @@ -105,7 +106,7 @@ class UpdateController extends Controller $this->repository->enable($currency); $this->repository->makePrimary($currency); - app('preferences')->mark(); + Preferences::mark(); $manager = $this->getManager(); $currency->refreshForUser($user); @@ -172,14 +173,13 @@ class UpdateController extends Controller $currency = $this->repository->update($currency, $data); - app('preferences')->mark(); + Preferences::mark(); $manager = $this->getManager(); $currency->refreshForUser($user); /** @var CurrencyTransformer $transformer */ $transformer = app(CurrencyTransformer::class); - $transformer->setParameters($this->parameters); $resource = new Item($currency, $transformer, 'currencies'); diff --git a/app/Api/V1/Controllers/Models/TransactionLink/DestroyController.php b/app/Api/V1/Controllers/Models/TransactionLink/DestroyController.php index e5c0b5ff46..25c2b85008 100644 --- a/app/Api/V1/Controllers/Models/TransactionLink/DestroyController.php +++ b/app/Api/V1/Controllers/Models/TransactionLink/DestroyController.php @@ -27,6 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Models\TransactionLink; use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -66,7 +67,7 @@ class DestroyController extends Controller public function destroy(TransactionJournalLink $link): JsonResponse { $this->repository->destroyLink($link); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/TransactionLinkType/DestroyController.php b/app/Api/V1/Controllers/Models/TransactionLinkType/DestroyController.php index 7abbff55d7..df18a2ba62 100644 --- a/app/Api/V1/Controllers/Models/TransactionLinkType/DestroyController.php +++ b/app/Api/V1/Controllers/Models/TransactionLinkType/DestroyController.php @@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\LinkType; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -73,7 +74,7 @@ class DestroyController extends Controller throw new FireflyException('200020: Link type cannot be changed.'); } $this->repository->destroy($linkType); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php index e689687467..5cdbb9023f 100644 --- a/app/Api/V1/Controllers/Models/UserGroup/IndexController.php +++ b/app/Api/V1/Controllers/Models/UserGroup/IndexController.php @@ -25,7 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers\Models\UserGroup; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Data\DateRequest; +use FireflyIII\Api\V1\Requests\PaginationRequest; use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface; use FireflyIII\Transformers\UserGroupTransformer; use Illuminate\Http\JsonResponse; @@ -52,17 +52,19 @@ class IndexController extends Controller ); } - public function index(DateRequest $request): JsonResponse + public function index(PaginationRequest $request): JsonResponse { $administrations = $this->repository->get(); - $pageSize = $this->parameters->get('limit'); + [ + 'page' => $page, + 'limit' => $limit, + 'offset' => $offset, + ] = $request->attributes->all(); $count = $administrations->count(); - $administrations = $administrations->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); - $paginator = new LengthAwarePaginator($administrations, $count, $pageSize, $this->parameters->get('page')); + $administrations = $administrations->slice($offset, $limit); + $paginator = new LengthAwarePaginator($administrations, $count, $limit, $page); $transformer = new UserGroupTransformer(); - $transformer->setParameters($this->parameters); // give params to transformer - return response() ->json($this->jsonApiList(self::RESOURCE_KEY, $paginator, $transformer)) ->header('Content-Type', self::CONTENT_TYPE) diff --git a/app/Api/V1/Controllers/Models/UserGroup/UpdateController.php b/app/Api/V1/Controllers/Models/UserGroup/UpdateController.php index e1a9e517e5..f118a5a960 100644 --- a/app/Api/V1/Controllers/Models/UserGroup/UpdateController.php +++ b/app/Api/V1/Controllers/Models/UserGroup/UpdateController.php @@ -28,6 +28,7 @@ use FireflyIII\Api\V1\Controllers\Controller; use FireflyIII\Api\V1\Requests\Models\UserGroup\UpdateRequest; use FireflyIII\Models\UserGroup; use FireflyIII\Repositories\UserGroup\UserGroupRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Transformers\UserGroupTransformer; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; @@ -59,7 +60,7 @@ class UpdateController extends Controller $data = $request->getData(); $userGroup = $this->repository->update($userGroup, $data); $userGroup->refresh(); - app('preferences')->mark(); + Preferences::mark(); $transformer = new UserGroupTransformer(); $transformer->setParameters($this->parameters); diff --git a/app/Api/V1/Controllers/Summary/BasicController.php b/app/Api/V1/Controllers/Summary/BasicController.php index 6b2acc86eb..86b65b9e34 100644 --- a/app/Api/V1/Controllers/Summary/BasicController.php +++ b/app/Api/V1/Controllers/Summary/BasicController.php @@ -27,7 +27,7 @@ namespace FireflyIII\Api\V1\Controllers\Summary; use Carbon\Carbon; use Exception; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Api\V1\Requests\Data\DateRequest; +use FireflyIII\Api\V1\Requests\Summary\BasicRequest; use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Enums\TransactionTypeEnum; use FireflyIII\Helpers\Collector\GroupCollectorInterface; @@ -88,34 +88,25 @@ class BasicController extends Controller ); } - /** - * This endpoint is documented at: - * https://api-docs.firefly-iii.org/?urls.primaryName=2.0.0%20(v1)#/summary/getBasicSummary - * - * @throws Exception - */ - public function basic(DateRequest $request): JsonResponse + public function basic(BasicRequest $request): JsonResponse { // parameters for boxes: - $dates = $request->getAll(); - $start = $dates['start']; - $end = $dates['end']; - $code = $request->get('currency_code'); + ['start' => $start, 'end' => $end, 'code' => $code] = $request->attributes->all(); // balance information: - $balanceData = $this->getBalanceInformation($start, $end); - $billData = $this->getSubscriptionInformation($start, $end); - $spentData = $this->getLeftToSpendInfo($start, $end); - $netWorthData = $this->getNetWorthInfo($end); + $balanceData = $this->getBalanceInformation($start, $end); + $billData = $this->getSubscriptionInformation($start, $end); + $spentData = $this->getLeftToSpendInfo($start, $end); + $netWorthData = $this->getNetWorthInfo($end); // $balanceData = []; // $billData = []; // $spentData = []; // $netWorthData = []; - $total = array_merge($balanceData, $billData, $spentData, $netWorthData); + $total = array_merge($balanceData, $billData, $spentData, $netWorthData); // give new keys - $return = []; + $return = []; foreach ($total as $entry) { - if (null === $code || ($code === $entry['currency_code'])) { + if ('' === $code || ($code === $entry['currency_code'])) { $return[$entry['key']] = $entry; } } @@ -589,8 +580,6 @@ class BasicController extends Controller private function getNetWorthInfo(Carbon $end): array { - $end->endOfDay(); - /** @var User $user */ $user = auth()->user(); Log::debug(sprintf('getNetWorthInfo up until "%s".', $end->format('Y-m-d H:i:s'))); diff --git a/app/Api/V1/Controllers/User/PreferencesController.php b/app/Api/V1/Controllers/User/PreferencesController.php index e5a97f7628..9c0be7163e 100644 --- a/app/Api/V1/Controllers/User/PreferencesController.php +++ b/app/Api/V1/Controllers/User/PreferencesController.php @@ -29,6 +29,7 @@ use FireflyIII\Api\V1\Requests\User\PreferenceStoreRequest; use FireflyIII\Api\V1\Requests\User\PreferenceUpdateRequest; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Preference; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Transformers\PreferenceTransformer; use Illuminate\Http\JsonResponse; use Illuminate\Pagination\LengthAwarePaginator; @@ -52,7 +53,7 @@ class PreferencesController extends Controller */ public function index(): JsonResponse { - $collection = app('preferences')->all(); + $collection = Preferences::all(); $manager = $this->getManager(); $count = $collection->count(); $pageSize = $this->parameters->get('limit'); @@ -110,7 +111,7 @@ class PreferencesController extends Controller throw new FireflyException('Please use api/v1/currencies/default instead.'); } - $pref = app('preferences')->set($data['name'], $data['data']); + $pref = Preferences::set($data['name'], $data['data']); /** @var PreferenceTransformer $transformer */ $transformer = app(PreferenceTransformer::class); @@ -135,7 +136,7 @@ class PreferencesController extends Controller $manager = $this->getManager(); $data = $request->getAll(); - $pref = app('preferences')->set($preference->name, $data['data']); + $pref = Preferences::set($preference->name, $data['data']); /** @var PreferenceTransformer $transformer */ $transformer = app(PreferenceTransformer::class); diff --git a/app/Api/V1/Controllers/Webhook/DestroyController.php b/app/Api/V1/Controllers/Webhook/DestroyController.php index 4899c7be20..98ee5f0586 100644 --- a/app/Api/V1/Controllers/Webhook/DestroyController.php +++ b/app/Api/V1/Controllers/Webhook/DestroyController.php @@ -30,6 +30,7 @@ use FireflyIII\Models\Webhook; use FireflyIII\Models\WebhookAttempt; use FireflyIII\Models\WebhookMessage; use FireflyIII\Repositories\Webhook\WebhookRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use Illuminate\Http\JsonResponse; use Illuminate\Support\Facades\Log; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; @@ -70,7 +71,7 @@ class DestroyController extends Controller Log::channel('audit')->info(sprintf('User destroys webhook #%d.', $webhook->id)); $this->repository->destroy($webhook); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } @@ -101,7 +102,7 @@ class DestroyController extends Controller Log::channel('audit')->info(sprintf('User destroys webhook #%d, message #%d, attempt #%d.', $webhook->id, $message->id, $attempt->id)); $this->repository->destroyAttempt($attempt); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } @@ -128,7 +129,7 @@ class DestroyController extends Controller Log::channel('audit')->info(sprintf('User destroys webhook #%d, message #%d.', $webhook->id, $message->id)); $this->repository->destroyMessage($message); - app('preferences')->mark(); + Preferences::mark(); return response()->json([], 204); } diff --git a/app/Api/V1/Requests/AggregateFormRequest.php b/app/Api/V1/Requests/AggregateFormRequest.php new file mode 100644 index 0000000000..001c902fda --- /dev/null +++ b/app/Api/V1/Requests/AggregateFormRequest.php @@ -0,0 +1,97 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; +use RuntimeException; + +abstract class AggregateFormRequest extends ApiRequest +{ + /** + * @var ApiRequest[] + */ + protected array $requests = []; + + /** @return class-string[] */ + abstract protected function getRequests(): array; + + public function initialize(array $query = [], array $request = [], array $attributes = [], array $cookies = [], array $files = [], array $server = [], $content = null): void + { + parent::initialize($query, $request, $attributes, $cookies, $files, $server, $content); + + // instantiate all subrequests and share current requests' bags with them + Log::debug('Initializing AggregateFormRequest.'); + foreach ($this->getRequests() as $config) { + $requestClass = is_array($config) ? array_shift($config) : $config; + + if (!is_a($requestClass, Request::class, true)) { + throw new RuntimeException('getRequests() must return class-strings of subclasses of Request'); + } + Log::debug(sprintf('Initializing subrequest %s', $requestClass)); + + $instance = $this->requests[] = new $requestClass(); + $instance->request = $this->request; + $instance->query = $this->query; + $instance->attributes = $this->attributes; + $instance->cookies = $this->cookies; + $instance->files = $this->files; + $instance->server = $this->server; + $instance->headers = $this->headers; + + if ($instance instanceof ApiRequest) { + $instance->handleConfig(is_array($config) ? $config : []); + } + } + Log::debug('Done initializing AggregateFormRequest.'); + } + + public function rules(): array + { + // check all subrequests for rules and combine them + return array_reduce( + $this->requests, + static fn (array $rules, FormRequest $request) => $rules + + ( + method_exists($request, 'rules') + ? $request->rules() + : [] + ), + [], + ); + } + + public function withValidator(Validator $validator): void + { + // register all subrequests' validators + foreach ($this->requests as $request) { + if (method_exists($request, 'withValidator')) { + Log::debug(sprintf('Process withValidator from class %s', get_class($request))); + $request->withValidator($validator); + } + } + } +} diff --git a/app/Api/V1/Requests/ApiRequest.php b/app/Api/V1/Requests/ApiRequest.php new file mode 100644 index 0000000000..0d2a343f81 --- /dev/null +++ b/app/Api/V1/Requests/ApiRequest.php @@ -0,0 +1,43 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Support\Request\ChecksLogin; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Foundation\Http\FormRequest; + +class ApiRequest extends FormRequest +{ + use ChecksLogin; + use ConvertsDataTypes; + + protected string $required = ''; + + public function handleConfig(array $config): void + { + if (in_array('required', $config, true)) { + $this->required = 'required'; + } + } +} diff --git a/app/Api/V1/Requests/Autocomplete/AutocompleteApiRequest.php b/app/Api/V1/Requests/Autocomplete/AutocompleteApiRequest.php new file mode 100644 index 0000000000..751f9f580b --- /dev/null +++ b/app/Api/V1/Requests/Autocomplete/AutocompleteApiRequest.php @@ -0,0 +1,46 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Autocomplete; + +use FireflyIII\Api\V1\Requests\AggregateFormRequest; +use FireflyIII\Api\V1\Requests\DateRequest; +use FireflyIII\Api\V1\Requests\Generic\QueryRequest; +use FireflyIII\Api\V1\Requests\Models\Account\AccountTypesApiRequest; +use FireflyIII\Api\V1\Requests\PaginationRequest; +use FireflyIII\Models\Account; +use Override; + +class AutocompleteApiRequest extends AggregateFormRequest +{ + #[Override] + protected function getRequests(): array + { + return [ + DateRequest::class, + [PaginationRequest::class, 'sort_class' => Account::class], + AccountTypesApiRequest::class, + QueryRequest::class, + ]; + } +} diff --git a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php index 566624b060..5dd6e8143e 100644 --- a/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php +++ b/app/Api/V1/Requests/Autocomplete/AutocompleteRequest.php @@ -31,6 +31,8 @@ use Illuminate\Foundation\Http\FormRequest; /** * Class AutocompleteRequest + * + * @deprecated */ class AutocompleteRequest extends FormRequest { @@ -48,10 +50,12 @@ class AutocompleteRequest extends FormRequest // remove 'initial balance' from allowed types. its internal $array = array_diff($array, [AccountTypeEnum::INITIAL_BALANCE->value, AccountTypeEnum::RECONCILIATION->value]); + $date = $this->getCarbonDate('date') ?? today(config('app.timezone')); + return [ 'types' => $array, 'query' => $this->convertString('query'), - 'date' => $this->getCarbonDate('date'), + 'date' => $date->endOfDay(), ]; } diff --git a/app/Api/V1/Requests/Data/DateRequest.php b/app/Api/V1/Requests/Data/DateRequest.php deleted file mode 100644 index 7155994862..0000000000 --- a/app/Api/V1/Requests/Data/DateRequest.php +++ /dev/null @@ -1,83 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Api\V1\Requests\Data; - -use FireflyIII\Exceptions\ValidationException; -use FireflyIII\Support\Request\ChecksLogin; -use FireflyIII\Support\Request\ConvertsDataTypes; -use Illuminate\Foundation\Http\FormRequest; - -/** - * Request class for end points that require date parameters. - * - * Class DateRequest - */ -class DateRequest extends FormRequest -{ - use ChecksLogin; - use ConvertsDataTypes; - - /** - * Get all data from the request. - */ - public function getAll(): array - { - $start = $this->getCarbonDate('start'); - $end = $this->getCarbonDate('end'); - if (null === $start) { - $start = now()->startOfMonth(); - } - if (null === $end) { - $end = now()->endOfMonth(); - } - // sanity check on dates: - [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - - - $start->startOfDay(); - $end->endOfDay(); - if ($start->diffInYears($end, true) > 5) { - throw new ValidationException('Date range out of range.'); - } - - return [ - 'start' => $start, - 'end' => $end, - 'date' => $this->getCarbonDate('date'), - ]; - } - - /** - * The rules that the incoming request must be matched against. - */ - public function rules(): array - { - return [ - 'date' => 'date|after:1970-01-02|before:2038-01-17', - 'start' => 'date|after:1970-01-02|before:2038-01-17|before:end|required_with:end', - 'end' => 'date|after:1970-01-02|before:2038-01-17|after:start|required_with:start', - ]; - } -} diff --git a/app/Api/V1/Requests/Data/SameDateRequest.php b/app/Api/V1/Requests/Data/SameDateRequest.php index 9f419653f7..49709b1b4d 100644 --- a/app/Api/V1/Requests/Data/SameDateRequest.php +++ b/app/Api/V1/Requests/Data/SameDateRequest.php @@ -32,6 +32,8 @@ use Illuminate\Foundation\Http\FormRequest; * Request class for end points that require date parameters. * * Class SameDateRequest + * + * @deprecated Replaced by DateRangeRequest */ class SameDateRequest extends FormRequest { diff --git a/app/Api/V1/Requests/DateRangeRequest.php b/app/Api/V1/Requests/DateRangeRequest.php new file mode 100644 index 0000000000..bfefb467a6 --- /dev/null +++ b/app/Api/V1/Requests/DateRangeRequest.php @@ -0,0 +1,57 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Illuminate\Validation\Validator; + +class DateRangeRequest extends ApiRequest +{ + public function rules(): array + { + return [ + 'start' => sprintf('date|after:1970-01-02|before:2038-01-17|before:end|required_with:end|%s', $this->required), + 'end' => sprintf('date|after:1970-01-02|before:2038-01-17|after:start|required_with:start|%s', $this->required), + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + // set null values + $this->attributes->set('start', null); + $this->attributes->set('end', null); + + return; + } + $start = $this->getCarbonDate('start')?->startOfDay(); + $end = $this->getCarbonDate('end')?->endOfDay(); + + $this->attributes->set('start', $start); + $this->attributes->set('end', $end); + } + ); + } +} diff --git a/app/Api/V1/Requests/DateRequest.php b/app/Api/V1/Requests/DateRequest.php new file mode 100644 index 0000000000..b73970786d --- /dev/null +++ b/app/Api/V1/Requests/DateRequest.php @@ -0,0 +1,58 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use Carbon\Carbon; +use Illuminate\Validation\Validator; + +class DateRequest extends ApiRequest +{ + public function rules(): array + { + return [ + 'date' => 'date|after:1970-01-02|before:2038-01-17|'.$this->required, + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + $date = $this->getCarbonDate('date')?->endOfDay(); + + // if we also have a range, date must be in that range + $start = $this->attributes->get('start'); + $end = $this->attributes->get('end'); + if ($date instanceof Carbon && $start instanceof Carbon && $end instanceof Carbon && !$date->between($start, $end)) { + $validator->errors()->add('date', (string)trans('validation.between_date')); + } + + $this->attributes->set('date', $date); + } + ); + } +} diff --git a/app/Api/V1/Requests/Generic/QueryRequest.php b/app/Api/V1/Requests/Generic/QueryRequest.php new file mode 100644 index 0000000000..6414cbbc64 --- /dev/null +++ b/app/Api/V1/Requests/Generic/QueryRequest.php @@ -0,0 +1,55 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Generic; + +use FireflyIII\Api\V1\Requests\ApiRequest; +use FireflyIII\Support\Request\ChecksLogin; +use FireflyIII\Support\Request\ConvertsDataTypes; +use Illuminate\Validation\Validator; + +class QueryRequest extends ApiRequest +{ + use ChecksLogin; + use ConvertsDataTypes; + + public function rules(): array + { + return [ + 'query' => sprintf('min:1|max:50|%s', $this->required), + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + $query = $this->convertString('query'); + $this->attributes->set('query', $query); + } + ); + } +} diff --git a/app/Api/V1/Requests/Generic/SingleDateRequest.php b/app/Api/V1/Requests/Generic/SingleDateRequest.php index 14e3c5fc02..b7fe750a5f 100644 --- a/app/Api/V1/Requests/Generic/SingleDateRequest.php +++ b/app/Api/V1/Requests/Generic/SingleDateRequest.php @@ -33,6 +33,8 @@ use Illuminate\Foundation\Http\FormRequest; * Request class for end points that require a date parameter. * * Class SingleDateRequest + * + * @deprecated */ class SingleDateRequest extends FormRequest { diff --git a/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php new file mode 100644 index 0000000000..e1e6bc686f --- /dev/null +++ b/app/Api/V1/Requests/Models/Account/AccountTypeApiRequest.php @@ -0,0 +1,57 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Models\Account; + +use FireflyIII\Api\V1\Requests\ApiRequest; +use FireflyIII\Support\Http\Api\AccountFilter; +use Illuminate\Validation\Validator; + +class AccountTypeApiRequest extends ApiRequest +{ + use AccountFilter; + + public function rules(): array + { + return [ + 'type' => sprintf('in:%s', implode(',', array_keys($this->types))), + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + + $type = $this->convertString('type', 'all'); + $this->attributes->add([ + 'type' => $type, + 'types' => $this->mapAccountTypes($type), + ]); + } + ); + } +} diff --git a/app/Api/V1/Requests/Models/Account/AccountTypesApiRequest.php b/app/Api/V1/Requests/Models/Account/AccountTypesApiRequest.php new file mode 100644 index 0000000000..e24a027974 --- /dev/null +++ b/app/Api/V1/Requests/Models/Account/AccountTypesApiRequest.php @@ -0,0 +1,58 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Models\Account; + +use FireflyIII\Api\V1\Requests\ApiRequest; +use FireflyIII\Rules\Account\IsValidAccountTypeList; +use FireflyIII\Support\Http\Api\AccountFilter; +use Illuminate\Validation\Validator; + +class AccountTypesApiRequest extends ApiRequest +{ + use AccountFilter; + + public function rules(): array + { + // sprintf('in:%s', implode(',', array_keys($this->types))), + return [ + 'types' => new IsValidAccountTypeList(), + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + + $type = $this->convertString('types', 'all'); + $this->attributes->add([ + 'types' => $this->mapAccountTypes($type), + ]); + } + ); + } +} diff --git a/app/Api/V1/Requests/Models/Account/ShowRequest.php b/app/Api/V1/Requests/Models/Account/ShowRequest.php index f4f7d73332..ed0a7dafe1 100644 --- a/app/Api/V1/Requests/Models/Account/ShowRequest.php +++ b/app/Api/V1/Requests/Models/Account/ShowRequest.php @@ -23,77 +23,21 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Account; -use Carbon\Carbon; +use FireflyIII\Api\V1\Requests\AggregateFormRequest; +use FireflyIII\Api\V1\Requests\DateRangeRequest; +use FireflyIII\Api\V1\Requests\DateRequest; +use FireflyIII\Api\V1\Requests\PaginationRequest; use FireflyIII\Models\Account; -use FireflyIII\Rules\IsValidSortInstruction; -use FireflyIII\Support\Facades\Preferences; -use FireflyIII\Support\Http\Api\AccountFilter; -use FireflyIII\Support\Request\ConvertsDataTypes; -use FireflyIII\User; -use Illuminate\Foundation\Http\FormRequest; -use Illuminate\Validation\Validator; -class ShowRequest extends FormRequest +class ShowRequest extends AggregateFormRequest { - use AccountFilter; - use ConvertsDataTypes; - - public function getParameters(): array + protected function getRequests(): array { - $limit = $this->convertInteger('limit'); - if (0 === $limit) { - // get default for user: - /** @var User $user */ - $user = auth()->user(); - $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; - } - - $page = $this->convertInteger('page'); - $page = min(max(1, $page), 2 ** 16); - return [ - 'type' => $this->convertString('type', 'all'), - 'limit' => $limit, - 'sort' => $this->convertSortParameters('sort', Account::class), - 'page' => $page, + [PaginationRequest::class, 'sort_class' => Account::class], + DateRangeRequest::class, + DateRequest::class, + AccountTypeApiRequest::class, ]; } - - public function rules(): array - { - $keys = implode(',', array_keys($this->types)); - - return [ - 'date' => 'date', - 'start' => 'date|present_with:end|before_or_equal:end|before:2038-01-17|after:1970-01-02', - 'end' => 'date|present_with:start|after_or_equal:start|before:2038-01-17|after:1970-01-02', - 'sort' => ['nullable', new IsValidSortInstruction(Account::class)], - 'type' => sprintf('in:%s', $keys), - 'limit' => 'numeric|min:1|max:131337', - 'page' => 'numeric|min:1|max:131337', - ]; - } - - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator): void { - if (count($validator->failed()) > 0) { - return; - } - $data = $validator->getData(); - - - if (array_key_exists('date', $data) && array_key_exists('start', $data) && array_key_exists('end', $data)) { - // assume valid dates, before we got here. - $start = Carbon::parse($data['start'], config('app.timezone'))->startOfDay(); - $end = Carbon::parse($data['end'], config('app.timezone'))->endOfDay(); - $date = Carbon::parse($data['date'], config('app.timezone')); - if (!$date->between($start, $end)) { - $validator->errors()->add('date', (string)trans('validation.between_date')); - } - } - } - ); - } } diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php new file mode 100644 index 0000000000..72a2c6bc59 --- /dev/null +++ b/app/Api/V1/Requests/Models/TransactionCurrency/CurrencyCodeRequest.php @@ -0,0 +1,50 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Models\TransactionCurrency; + +use FireflyIII\Api\V1\Requests\ApiRequest; +use Illuminate\Validation\Validator; + +class CurrencyCodeRequest extends ApiRequest +{ + public function rules(): array + { + return [ + 'code' => sprintf('exists:transaction_currencies,code|%s', $this->required), + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if (!$validator->valid()) { + return; + } + $code = $this->convertString('code', ''); + $this->attributes->set('code', $code); + } + ); + } +} diff --git a/app/Api/V1/Requests/PaginationRequest.php b/app/Api/V1/Requests/PaginationRequest.php new file mode 100644 index 0000000000..9b080007dc --- /dev/null +++ b/app/Api/V1/Requests/PaginationRequest.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Rules\IsValidSortInstruction; +use FireflyIII\Support\Facades\Preferences; +use FireflyIII\User; +use Illuminate\Validation\Validator; +use RuntimeException; + +class PaginationRequest extends ApiRequest +{ + private ?string $sortClass = null; + + public function handleConfig(array $config): void + { + parent::handleConfig($config); + + $this->sortClass = $config['sort_class'] ?? null; + + if (!$this->sortClass) { + throw new RuntimeException('PaginationRequest requires a sort_class config'); + } + } + + public function rules(): array + { + return [ + 'sort' => ['nullable', new IsValidSortInstruction((string)$this->sortClass)], + 'limit' => 'numeric|min:1|max:131337', + 'page' => 'numeric|min:1|max:131337', + ]; + } + + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + if ($validator->failed()) { + return; + } + + $limit = $this->convertInteger('limit'); + if (0 === $limit) { + // get default for user: + /** @var User $user */ + $user = auth()->user(); + $limit = (int)Preferences::getForUser($user, 'listPageSize', 50)->data; + } + + $page = $this->convertInteger('page'); + $page = min(max(1, $page), 2 ** 16); + $offset = ($page - 1) * $limit; + $sort = $this->sortClass ? $this->convertSortParameters('sort', $this->sortClass) : $this->get('sort'); + + $this->attributes->set('limit', $limit); + $this->attributes->set('sort', $sort); + $this->attributes->set('page', $page); + $this->attributes->set('offset', $offset); + } + ); + } +} diff --git a/app/Api/V1/Requests/Summary/BasicRequest.php b/app/Api/V1/Requests/Summary/BasicRequest.php new file mode 100644 index 0000000000..ce6ff670c0 --- /dev/null +++ b/app/Api/V1/Requests/Summary/BasicRequest.php @@ -0,0 +1,39 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests\Summary; + +use FireflyIII\Api\V1\Requests\AggregateFormRequest; +use FireflyIII\Api\V1\Requests\DateRangeRequest; +use FireflyIII\Api\V1\Requests\Models\TransactionCurrency\CurrencyCodeRequest; + +class BasicRequest extends AggregateFormRequest +{ + protected function getRequests(): array + { + return [ + [DateRangeRequest::class, 'required'], + CurrencyCodeRequest::class, + ]; + } +} diff --git a/app/Console/Commands/Correction/CorrectsFrontpageAccounts.php b/app/Console/Commands/Correction/CorrectsFrontpageAccounts.php index 11a2926f6e..ab2b522115 100644 --- a/app/Console/Commands/Correction/CorrectsFrontpageAccounts.php +++ b/app/Console/Commands/Correction/CorrectsFrontpageAccounts.php @@ -28,6 +28,7 @@ use FireflyIII\Console\Commands\ShowsFriendlyMessages; use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Models\Preference; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Console\Command; @@ -47,7 +48,7 @@ class CorrectsFrontpageAccounts extends Command /** @var User $user */ foreach ($users as $user) { - $preference = app('preferences')->getForUser($user, 'frontpageAccounts'); + $preference = Preferences::getForUser($user, 'frontpageAccounts'); if (null !== $preference) { $this->fixPreference($preference); } @@ -79,6 +80,6 @@ class CorrectsFrontpageAccounts extends Command } } } - app('preferences')->setForUser($preference->user, 'frontpageAccounts', $fixed); + Preferences::setForUser($preference->user, 'frontpageAccounts', $fixed); } } diff --git a/app/Console/Commands/Correction/CreatesAccessTokens.php b/app/Console/Commands/Correction/CreatesAccessTokens.php index 92603c83a6..ef478d8da2 100644 --- a/app/Console/Commands/Correction/CreatesAccessTokens.php +++ b/app/Console/Commands/Correction/CreatesAccessTokens.php @@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Correction; use Exception; use FireflyIII\Console\Commands\ShowsFriendlyMessages; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Console\Command; @@ -54,10 +55,10 @@ class CreatesAccessTokens extends Command /** @var User $user */ foreach ($users as $user) { - $pref = app('preferences')->getForUser($user, 'access_token'); + $pref = Preferences::getForUser($user, 'access_token'); if (null === $pref) { $token = $user->generateAccessToken(); - app('preferences')->setForUser($user, 'access_token', $token); + Preferences::setForUser($user, 'access_token', $token); $this->friendlyInfo(sprintf('Generated access token for user %s', $user->email)); ++$count; } diff --git a/app/Console/Commands/System/SetsLatestVersion.php b/app/Console/Commands/System/SetsLatestVersion.php index 3c6f6b5244..823f69fec6 100644 --- a/app/Console/Commands/System/SetsLatestVersion.php +++ b/app/Console/Commands/System/SetsLatestVersion.php @@ -46,7 +46,7 @@ class SetsLatestVersion extends Command return 0; } - FireflyConfig::set('ff3_version', config('firefly.version')); + FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); $this->friendlyInfo('Updated version.'); return 0; diff --git a/app/Console/Commands/Upgrade/UpgradesBillsToRules.php b/app/Console/Commands/Upgrade/UpgradesBillsToRules.php index a244f74738..7575dc31e3 100644 --- a/app/Console/Commands/Upgrade/UpgradesBillsToRules.php +++ b/app/Console/Commands/Upgrade/UpgradesBillsToRules.php @@ -33,6 +33,7 @@ use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Console\Command; @@ -114,7 +115,7 @@ class UpgradesBillsToRules extends Command $this->ruleRepository->setUser($user); /** @var Preference $lang */ - $lang = app('preferences')->getForUser($user, 'language', 'en_US'); + $lang = Preferences::getForUser($user, 'language', 'en_US'); $language = null !== $lang->data && !is_array($lang->data) ? (string) $lang->data : 'en_US'; $groupTitle = (string) trans('firefly.rulegroup_for_bills_title', [], $language); $ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle); diff --git a/app/Console/Commands/Upgrade/UpgradesDatabase.php b/app/Console/Commands/Upgrade/UpgradesDatabase.php index 32ff857c9e..877232ee58 100644 --- a/app/Console/Commands/Upgrade/UpgradesDatabase.php +++ b/app/Console/Commands/Upgrade/UpgradesDatabase.php @@ -87,7 +87,7 @@ class UpgradesDatabase extends Command $this->call($command, $args); } // index will set FF3 version. - FireflyConfig::set('ff3_version', (string) config('firefly.version')); + FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); return 0; } diff --git a/app/Console/Commands/VerifiesAccessToken.php b/app/Console/Commands/VerifiesAccessToken.php index 906980df68..3726eb6a36 100644 --- a/app/Console/Commands/VerifiesAccessToken.php +++ b/app/Console/Commands/VerifiesAccessToken.php @@ -26,6 +26,7 @@ namespace FireflyIII\Console\Commands; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Support\Facades\Log; @@ -79,7 +80,7 @@ trait VerifiesAccessToken return false; } - $accessToken = app('preferences')->getForUser($user, 'access_token'); + $accessToken = Preferences::getForUser($user, 'access_token'); if (null === $accessToken) { Log::error(sprintf('User #%d has no access token, so cannot access command line options.', $userId)); diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php index 421af6dca2..74e8643886 100644 --- a/app/Factory/AttachmentFactory.php +++ b/app/Factory/AttachmentFactory.php @@ -84,8 +84,10 @@ class AttachmentFactory return $attachment; } - public function setUser(User $user): void + public function setUser(User $user): static { $this->user = $user; + + return $this; } } diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php index 52643c55f7..e4635fb93c 100644 --- a/app/Factory/PiggyBankEventFactory.php +++ b/app/Factory/PiggyBankEventFactory.php @@ -26,6 +26,7 @@ namespace FireflyIII\Factory; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use Illuminate\Support\Facades\Log; /** * Create piggy bank events. @@ -36,9 +37,9 @@ class PiggyBankEventFactory { public function create(TransactionJournal $journal, ?PiggyBank $piggyBank): void { - app('log')->debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type)); + Log::debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type)); if (!$piggyBank instanceof PiggyBank) { - app('log')->debug('Piggy bank is null'); + Log::debug('Piggy bank is null'); return; } @@ -49,7 +50,7 @@ class PiggyBankEventFactory $amount = $piggyRepos->getExactAmount($piggyBank, $journal); if (0 === bccomp($amount, '0')) { - app('log')->debug('Amount is zero, will not create event.'); + Log::debug('Amount is zero, will not create event.'); return; } diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index d11753a2bf..cd47828c9d 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -221,10 +221,26 @@ class TransactionJournalFactory ]; Log::debug('Source info:', $sourceInfo); Log::debug('Destination info:', $destInfo); - $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo); - $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount); + $destinationAccount = null; + $sourceAccount = null; + if (TransactionTypeEnum::DEPOSIT->value === $type->type) { + Log::debug('Transaction type is deposit, start with destination first.'); + $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo); + $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo, $destinationAccount); + } + if (TransactionTypeEnum::DEPOSIT->value !== $type->type) { + Log::debug('Transaction type is not deposit, start with source first.'); + $sourceAccount = $this->getAccount($type->type, 'source', $sourceInfo); + $destinationAccount = $this->getAccount($type->type, 'destination', $destInfo, $sourceAccount); + } + Log::debug('Done with getAccount(2x)'); + // there is a safety catch here. If either account is NULL, they will be replaced with the cash account. + if (null === $destinationAccount) { + Log::warning('Destination account is NULL, will replace with cash account.'); + $destinationAccount = $this->accountRepository->getCashAccount(); + } // this is the moment for a reconciliation sanity check (again). if (TransactionTypeEnum::RECONCILIATION->value === $type->type) { diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index a0aea0c9df..b920ab172e 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -137,9 +137,12 @@ class MonthReportGenerator implements ReportGeneratorInterface ; $journals = $collector->getExtractedJournals(); $journals = array_reverse($journals, true); - // this call is correct. - Log::debug(sprintf('getAuditReport: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $dayBeforeBalance = Steam::finalAccountBalance($account, $date); + + Log::debug(sprintf('getAuditReport: Call accountsBalancesOptimized with date/time "%s"', $date->toIso8601String())); + // 2025-10-08 replace with accountsBalancesOptimized. + // $dayBeforeBalance = Steam::finalAccountBalance($account, $date); + $dayBeforeBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]; + $startBalance = $dayBeforeBalance['balance']; $primaryCurrency = app('amount')->getPrimaryCurrencyByUserGroup($account->user->userGroup); $currency = $accountRepository->getAccountCurrency($account) ?? $primaryCurrency; @@ -176,12 +179,14 @@ class MonthReportGenerator implements ReportGeneratorInterface // call is correct. Log::debug(sprintf('getAuditReport end: Call finalAccountBalance with date/time "%s"', $this->end->toIso8601String())); + // 2025-10-08 replace with accountsBalancesOptimized: return [ 'journals' => $journals, 'currency' => $currency, 'exists' => 0 !== count($journals), 'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), - 'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'], + // 'endBalance' => Steam::finalAccountBalance($account, $this->end)['balance'], + 'endBalance' => Steam::accountsBalancesOptimized(new Collection()->push($account), $this->end)[$account->id]['balance'], 'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)), 'dayBeforeBalance' => $dayBeforeBalance, ]; diff --git a/app/Handlers/Events/AutomationHandler.php b/app/Handlers/Events/AutomationHandler.php index 7a59a78a10..9d60e4dc5b 100644 --- a/app/Handlers/Events/AutomationHandler.php +++ b/app/Handlers/Events/AutomationHandler.php @@ -30,6 +30,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; use FireflyIII\Notifications\User\TransactionCreation; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\Transformers\TransactionGroupTransformer; use Illuminate\Support\Facades\Notification; @@ -52,7 +53,7 @@ class AutomationHandler $user = $repository->find($event->userId); /** @var bool $sendReport */ - $sendReport = app('preferences')->getForUser($user, 'notification_transaction_creation', false)->data; + $sendReport = Preferences::getForUser($user, 'notification_transaction_creation', false)->data; if (false === $sendReport) { app('log')->debug('Not sending report, because config says so.'); diff --git a/app/Handlers/Events/Model/RuleHandler.php b/app/Handlers/Events/Model/RuleHandler.php index ea3bb107c8..9655368b52 100644 --- a/app/Handlers/Events/Model/RuleHandler.php +++ b/app/Handlers/Events/Model/RuleHandler.php @@ -27,6 +27,7 @@ namespace FireflyIII\Handlers\Events\Model; use FireflyIII\Events\Model\Rule\RuleActionFailedOnArray; use FireflyIII\Events\Model\Rule\RuleActionFailedOnObject; use FireflyIII\Notifications\User\RuleActionFailed; +use FireflyIII\Support\Facades\Preferences; use GuzzleHttp\Exception\ClientException; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Notification; @@ -42,7 +43,7 @@ class RuleHandler $rule = $ruleAction->rule; /** @var bool $preference */ - $preference = app('preferences')->getForUser($rule->user, 'notification_rule_action_failures', true)->data; + $preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data; if (false === $preference) { return; } @@ -71,7 +72,7 @@ class RuleHandler $rule = $ruleAction->rule; /** @var bool $preference */ - $preference = app('preferences')->getForUser($rule->user, 'notification_rule_action_failures', true)->data; + $preference = Preferences::getForUser($rule->user, 'notification_rule_action_failures', true)->data; if (false === $preference) { return; } diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php index b960ff88df..fd33908b32 100644 --- a/app/Handlers/Events/StoredGroupEventHandler.php +++ b/app/Handlers/Events/StoredGroupEventHandler.php @@ -27,6 +27,7 @@ use FireflyIII\Enums\WebhookTrigger; use FireflyIII\Events\RequestedSendWebhookMessages; use FireflyIII\Events\StoredTransactionGroup; use FireflyIII\Generator\Webhook\MessageGeneratorInterface; +use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; @@ -105,10 +106,18 @@ class StoredGroupEventHandler /** @var TransactionJournal $journal */ foreach ($event->transactionGroup->transactionJournals as $journal) { + /** @var null|Transaction $source */ $source = $journal->transactions()->where('amount', '<', '0')->first(); + + /** @var null|Transaction $dest */ $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; $budgets = $journal->budgets; diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index db1fdd4134..9e9f0d25cf 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -51,6 +51,7 @@ use FireflyIII\Notifications\User\UserLogin; use FireflyIII\Notifications\User\UserNewPassword; use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotification; use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\Support\Facades\Preferences; use FireflyIII\User; use Illuminate\Auth\Events\Login; use Illuminate\Support\Facades\Log; @@ -174,9 +175,10 @@ class UserEventHandler $user = $event->user; if ($repository->hasRole($user, 'demo')) { // set user back to English. - app('preferences')->setForUser($user, 'language', 'en_US'); - app('preferences')->setForUser($user, 'locale', 'equal'); - app('preferences')->mark(); + Preferences::setForUser($user, 'language', 'en_US'); + Preferences::setForUser($user, 'locale', 'equal'); + Preferences::setForUser($user, 'anonymous', false); + Preferences::mark(); } } @@ -188,7 +190,7 @@ class UserEventHandler return; // do not email demo user. } - $list = app('preferences')->getForUser($user, 'login_ip_history', [])->data; + $list = Preferences::getForUser($user, 'login_ip_history', [])->data; if (!is_array($list)) { $list = []; } @@ -217,12 +219,12 @@ class UserEventHandler $list[$index]['notified'] = true; } - app('preferences')->setForUser($user, 'login_ip_history', $list); + Preferences::setForUser($user, 'login_ip_history', $list); } public function sendAdminRegistrationNotification(RegisteredUser $event): void { - $sendMail = (bool) app('fireflyconfig')->get('notification_admin_new_reg', true)->data; + $sendMail = (bool)app('fireflyconfig')->get('notification_admin_new_reg', true)->data; if ($sendMail) { $owner = $event->owner; @@ -257,7 +259,7 @@ class UserEventHandler $newEmail = $event->newEmail; $oldEmail = $event->oldEmail; $user = $event->user; - $token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid'); + $token = Preferences::getForUser($user, 'email_change_confirm_token', 'invalid'); $url = route('profile.confirm-email-change', [$token->data]); try { @@ -281,8 +283,8 @@ class UserEventHandler $newEmail = $event->newEmail; $oldEmail = $event->oldEmail; $user = $event->user; - $token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid'); - $hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail)); + $token = Preferences::getForUser($user, 'email_change_undo_token', 'invalid'); + $hashed = hash('sha256', sprintf('%s%s', (string)config('app.key'), $oldEmail)); $url = route('profile.undo-email-change', [$token->data, $hashed]); try { @@ -365,7 +367,7 @@ class UserEventHandler */ public function sendRegistrationMail(RegisteredUser $event): void { - $sendMail = (bool) app('fireflyconfig')->get('notification_user_new_reg', true)->data; + $sendMail = (bool)app('fireflyconfig')->get('notification_user_new_reg', true)->data; if ($sendMail) { try { Notification::send($event->user, new UserRegistrationNotification()); @@ -455,7 +457,7 @@ class UserEventHandler try { /** @var array $preference */ - $preference = app('preferences')->getForUser($user, 'login_ip_history', [])->data; + $preference = Preferences::getForUser($user, 'login_ip_history', [])->data; } catch (FireflyException $e) { // don't care. app('log')->error($e->getMessage()); @@ -491,8 +493,8 @@ class UserEventHandler $preference = array_values($preference); /** @var bool $send */ - $send = app('preferences')->getForUser($user, 'notification_user_login', true)->data; - app('preferences')->setForUser($user, 'login_ip_history', $preference); + $send = Preferences::getForUser($user, 'notification_user_login', true)->data; + Preferences::setForUser($user, 'login_ip_history', $preference); if (false === $inArray && true === $send) { event(new DetectedNewIPAddress($user)); diff --git a/app/Helpers/Collector/Extensions/AccountCollection.php b/app/Helpers/Collector/Extensions/AccountCollection.php index d56ee1de4b..203cad80ab 100644 --- a/app/Helpers/Collector/Extensions/AccountCollection.php +++ b/app/Helpers/Collector/Extensions/AccountCollection.php @@ -62,12 +62,13 @@ trait AccountCollection if (null === $account) { continue; } + + // 2025-10-08 replace with accountsBalancesOptimized // the balance must be found BEFORE the transaction date. - // so sub one second. This is not perfect, but works well enough. - $date = clone $transaction['date']; - $date->subSecond(); - Log::debug(sprintf('accountBalanceIs: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $balance = Steam::finalAccountBalance($account, $date); + // so inclusive = false + Log::debug(sprintf('accountBalanceIs: Call accountsBalancesOptimized with date/time "%s"', $transaction['date']->toIso8601String())); + $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $transaction['date'], convertToPrimary: null, inclusive: false)[$account->id]; + // $balance = Steam::finalAccountBalance($account, $date); $result = bccomp((string) $balance['balance'], $value); Log::debug(sprintf('"%s" vs "%s" is %d', $balance['balance'], $value, $result)); diff --git a/app/Helpers/Fiscal/FiscalHelper.php b/app/Helpers/Fiscal/FiscalHelper.php index 53aad1fef3..c9450d597e 100644 --- a/app/Helpers/Fiscal/FiscalHelper.php +++ b/app/Helpers/Fiscal/FiscalHelper.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Fiscal; use Carbon\Carbon; +use FireflyIII\Support\Facades\Preferences; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -40,7 +41,7 @@ class FiscalHelper implements FiscalHelperInterface */ public function __construct() { - $this->useCustomFiscalYear = (bool) app('preferences')->get('customFiscalYear', false)->data; + $this->useCustomFiscalYear = (bool) Preferences::get('customFiscalYear', false)->data; } /** @@ -74,7 +75,7 @@ class FiscalHelper implements FiscalHelperInterface // get start mm-dd. Then create a start date in the year passed. $startDate = clone $date; if (true === $this->useCustomFiscalYear) { - $prefStartStr = app('preferences')->get('fiscalYearStart', '01-01')->data; + $prefStartStr = Preferences::get('fiscalYearStart', '01-01')->data; if (is_array($prefStartStr)) { $prefStartStr = '01-01'; } diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php index 0ea1ce6cfe..b45659415c 100644 --- a/app/Helpers/Report/NetWorth.php +++ b/app/Helpers/Report/NetWorth.php @@ -25,8 +25,6 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Report; use Carbon\Carbon; -use Deprecated; -use FireflyIII\Enums\AccountTypeEnum; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\UserGroup; @@ -130,55 +128,4 @@ class NetWorth implements NetWorthInterface $this->accountRepository->setUserGroup($userGroup); } - - #[Deprecated] - public function sumNetWorthByCurrency(Carbon $date): array - { - /** - * Collect accounts - */ - $accounts = $this->getAccounts(); - $return = []; - Log::debug(sprintf('SumNetWorth: accountsBalancesOptimized("%s")', $date->format('Y-m-d H:i:s'))); - $balances = Steam::accountsBalancesOptimized($accounts, $date); - foreach ($accounts as $account) { - $currency = $this->accountRepository->getAccountCurrency($account); - $balance = $balances[$account->id]['balance'] ?? '0'; - - // always subtract virtual balance. - $virtualBalance = $account->virtual_balance; - if ('' !== $virtualBalance) { - $balance = bcsub($balance, (string) $virtualBalance); - } - - $return[$currency->id] ??= [ - 'id' => (string) $currency->id, - 'name' => $currency->name, - 'symbol' => $currency->symbol, - 'code' => $currency->code, - 'decimal_places' => $currency->decimal_places, - 'sum' => '0', - ]; - $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], (string) $balance); - } - - return $return; - } - - private function getAccounts(): Collection - { - $accounts = $this->accountRepository->getAccountsByType( - [AccountTypeEnum::ASSET->value, AccountTypeEnum::DEFAULT->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value] - ); - $filtered = new Collection(); - - /** @var Account $account */ - foreach ($accounts as $account) { - if (1 === (int) $this->accountRepository->getMetaValue($account, 'include_net_worth')) { - $filtered->push($account); - } - } - - return $filtered; - } } diff --git a/app/Helpers/Report/NetWorthInterface.php b/app/Helpers/Report/NetWorthInterface.php index 421caa9a74..3870359766 100644 --- a/app/Helpers/Report/NetWorthInterface.php +++ b/app/Helpers/Report/NetWorthInterface.php @@ -25,7 +25,6 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Report; use Carbon\Carbon; -use Deprecated; use FireflyIII\Models\UserGroup; use FireflyIII\User; use Illuminate\Contracts\Auth\Authenticatable; @@ -49,12 +48,4 @@ interface NetWorthInterface public function setUser(Authenticatable|User|null $user): void; public function setUserGroup(UserGroup $userGroup): void; - - /** - * TODO move to repository - * - * Same as above but cleaner function with less dependencies. - */ - #[Deprecated] - public function sumNetWorthByCurrency(Carbon $date): array; } diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php index bab54065d9..f246ec119a 100644 --- a/app/Http/Controllers/Account/CreateController.php +++ b/app/Http/Controllers/Account/CreateController.php @@ -96,11 +96,10 @@ class CreateController extends Controller ]; // interest calculation periods: - $interestPeriods = [ - 'daily' => (string) trans('firefly.interest_calc_daily'), - 'monthly' => (string) trans('firefly.interest_calc_monthly'), - 'yearly' => (string) trans('firefly.interest_calc_yearly'), - ]; + $interestPeriods = []; + foreach (config('firefly.interest_periods') as $period) { + $interestPeriods[$period] = trans(sprintf('firefly.interest_calc_%s', $period)); + } // pre fill some data $request->session()->flash( diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index 8c2d083b13..041b34a8f9 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -108,11 +108,10 @@ class EditController extends Controller ]; // interest calculation periods: - $interestPeriods = [ - 'daily' => (string) trans('firefly.interest_calc_daily'), - 'monthly' => (string) trans('firefly.interest_calc_monthly'), - 'yearly' => (string) trans('firefly.interest_calc_yearly'), - ]; + $interestPeriods = []; + foreach (config('firefly.interest_periods') as $period) { + $interestPeriods[$period] = trans(sprintf('firefly.interest_calc_%s', $period)); + } // put previous url in session if not redirect from store (not "return_to_edit"). if (true !== session('accounts.edit.fromUpdate')) { diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index 4f34f0dd5f..41ee6981b3 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -74,32 +74,30 @@ class IndexController extends Controller */ public function inactive(Request $request, string $objectType) { - $inactivePage = true; - $subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); - $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); - $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); - $collection = $this->repository->getInactiveAccountsByType($types); - $total = $collection->count(); - $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); - $pageSize = (int) app('preferences')->get('listPageSize', 50)->data; - $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); + $inactivePage = true; + $subTitle = (string) trans(sprintf('firefly.%s_accounts_inactive', $objectType)); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); + $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); + $collection = $this->repository->getInactiveAccountsByType($types); + $total = $collection->count(); + $page = 0 === (int) $request->get('page') ? 1 : (int) $request->get('page'); + $pageSize = (int) app('preferences')->get('listPageSize', 50)->data; + $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); unset($collection); /** @var Carbon $start */ - $start = clone session('start', today(config('app.timezone'))->startOfMonth()); + $start = clone session('start', today(config('app.timezone'))->startOfMonth()); /** @var Carbon $end */ - $end = clone session('end', today(config('app.timezone'))->endOfMonth()); + $end = clone session('end', today(config('app.timezone'))->endOfMonth()); - // #10618 go to the end of the previous day. - $start->subSecond(); - - $ids = $accounts->pluck('id')->toArray(); - Log::debug(sprintf('inactive start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); - Log::debug(sprintf('inactive end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); - $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); - $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); - $activities = Steam::getLastActivities($ids); + $ids = $accounts->pluck('id')->toArray(); + Log::debug(sprintf('inactive start: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + [ + $startBalances, + $endBalances, + ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); + $activities = Steam::getLastActivities($ids); $accounts->each( @@ -119,7 +117,7 @@ class IndexController extends Controller ); // make paginator: - $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); + $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); $accounts->setPath(route('accounts.inactive.index', [$objectType])); return view('accounts.index', compact('objectType', 'inactivePage', 'subTitleIcon', 'subTitle', 'page', 'accounts')); @@ -169,14 +167,12 @@ class IndexController extends Controller /** @var Carbon $end */ $end = clone session('end', today(config('app.timezone'))->endOfMonth()); - // #10618 go to the end of the previous day. - $start->subSecond(); - $ids = $accounts->pluck('id')->toArray(); - Log::debug(sprintf('index start: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); - Log::debug(sprintf('index end: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); - $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); - $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); + Log::debug(sprintf('index: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + [ + $startBalances, + $endBalances, + ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); $activities = Steam::getLastActivities($ids); diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index cee8c1c158..c78696f776 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -39,6 +39,7 @@ use FireflyIII\User; use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Routing\Redirector; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Illuminate\View\View; @@ -113,13 +114,20 @@ class ReconcileController extends Controller $start->startOfDay(); $end->endOfDay(); - $startDate = clone $start; - $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59 + // $startDate = clone $start; + // $startDate->subDay()->endOfDay(); // this is correct, subday endofday ends at 23:59:59 // both are validated and are correct. - Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String())); - Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); - $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); - $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); + // Log::debug(sprintf('reconcile: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String())); + // Log::debug(sprintf('reconcile2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); + // $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); + // $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); + + // 2025-10-08 replace with accountsBalancesOptimized + // no longer need to do subday->endofday on $start, set inclusive = false for the same effect. + $startBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $start, convertToPrimary: null, inclusive: false)[$account->id]['balance'], $currency->decimal_places); + $endBalance = Steam::bcround(Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]['balance'], $currency->decimal_places); + + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $subTitle = (string) trans('firefly.reconcile_account', ['account' => $account->name]); diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index 9b1a6922f6..bcb0ba6a0e 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -169,8 +169,9 @@ class ShowController extends Controller $now = $end; } - Log::debug(sprintf('show: Call finalAccountBalance with date/time "%s"', $now->toIso8601String())); - $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency); + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $now)[$account->id]; + // $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $now), $account, $this->convertToPrimary, $accountCurrency); return view( 'accounts.show', @@ -237,8 +238,12 @@ class ShowController extends Controller $chartUrl = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $showAll = true; // correct - Log::debug(sprintf('showAll: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); - $balances = Steam::filterAccountBalance(Steam::finalAccountBalance($account, $end), $account, $this->convertToPrimary, $accountCurrency); + Log::debug(sprintf('showAll: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String())); + + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + // $balances = Steam::finalAccountBalance($account, $end); + // $balances = Steam::filterAccountBalance($balances, $account, $this->convertToPrimary, $accountCurrency); + $balances = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]; return view( 'accounts.show', diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 3df8e35845..ada7f9014e 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -116,10 +116,11 @@ class AccountController extends Controller $accountNames = $this->extractNames($accounts); // grab all balances - Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); - Log::debug(sprintf('expenseAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); - $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); - $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); + Log::debug(sprintf('expenseAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + [ + $startBalances, + $endBalances, + ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); Log::debug('Done collecting balances'); // loop the accounts, then check for balance and currency info. foreach ($accounts as $account) { @@ -521,9 +522,9 @@ class AccountController extends Controller $range = Steam::filterAccountBalances($range, $account, $this->convertToPrimary, $accountCurrency); Log::debug('Get and filter balance for entire range end'); // temp, get end balance. - Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); - Steam::finalAccountBalance($account, $end); - Log::debug('END temp get end balance done'); + // Log::debug(sprintf('period: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); + // Steam::finalAccountBalance($account, $end); + // Log::debug('END temp get end balance done'); $previous = array_values($range)[0]; $accountCurrency ??= $this->primaryCurrency; // do this AFTER getting the balances. @@ -666,10 +667,11 @@ class AccountController extends Controller $accountNames = $this->extractNames($accounts); // grab all balances - Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $start->format('Y-m-d H:i:s'))); - Log::debug(sprintf('revAccounts: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); - $startBalances = Steam::accountsBalancesOptimized($accounts, $start, $this->primaryCurrency, $this->convertToPrimary); - $endBalances = Steam::accountsBalancesOptimized($accounts, $end, $this->primaryCurrency, $this->convertToPrimary); + Log::debug(sprintf('revAccounts: accountsBalancesInRange("%s", "%s")', $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + [ + $startBalances, + $endBalances, + ] = Steam::accountsBalancesInRange($accounts, $start, $end, $this->primaryCurrency, $this->convertToPrimary); // loop the accounts, then check for balance and currency info. diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 534acf8488..3a148e0168 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -77,6 +77,7 @@ abstract class Controller extends BaseController View::share('DEMO_USERNAME', config('firefly.demo_username')); View::share('DEMO_PASSWORD', config('firefly.demo_password')); View::share('FF_VERSION', config('firefly.version')); + View::share('FF_BUILD_TIME', config('firefly.build_time')); // is webhooks enabled? View::share('featuringWebhooks', true === config('firefly.feature_flags.webhooks') && true === config('firefly.allow_webhooks')); @@ -131,6 +132,7 @@ abstract class Controller extends BaseController $this->primaryCurrency = null; // get shown-intro-preference: if (auth()->check()) { + View::share('anonymous', Steam::anonymous()); $this->primaryCurrency = Amount::getPrimaryCurrency(); $language = Steam::getLanguage(); $locale = Steam::getLocale(); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index 1ed00b4710..0e6bddcd2d 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -81,11 +81,11 @@ class DebugController extends Controller */ public function displayError(): void { - app('log')->debug('This is a test message at the DEBUG level.'); - app('log')->info('This is a test message at the INFO level.'); + Log::debug('This is a test message at the DEBUG level.'); + Log::info('This is a test message at the INFO level.'); Log::notice('This is a test message at the NOTICE level.'); - app('log')->warning('This is a test message at the WARNING level.'); - app('log')->error('This is a test message at the ERROR level.'); + Log::warning('This is a test message at the WARNING level.'); + Log::error('This is a test message at the ERROR level.'); Log::critical('This is a test message at the CRITICAL level.'); Log::alert('This is a test message at the ALERT level.'); Log::emergency('This is a test message at the EMERGENCY level.'); @@ -187,6 +187,8 @@ class DebugController extends Controller return [ 'php_version' => PHP_VERSION, 'php_os' => PHP_OS, + 'build_time' => config('firefly.build_time'), + 'build_time_nice' => Carbon::parse(config('firefly.build_time'), 'Europe/Amsterdam')->setTimezone('Europe/Amsterdam')->format('Y-m-d H:i:s e'), 'uname' => php_uname('m'), 'interface' => PHP_SAPI, 'bits' => PHP_INT_SIZE * 8, @@ -212,11 +214,11 @@ class DebugController extends Controller try { if (file_exists('/var/www/counter-main.txt')) { $return['build'] = trim(file_get_contents('/var/www/counter-main.txt')); - app('log')->debug(sprintf('build is now "%s"', $return['build'])); + Log::debug(sprintf('build is now "%s"', $return['build'])); } } catch (Exception $e) { - app('log')->debug('Could not check build counter, but thats ok.'); - app('log')->warning($e->getMessage()); + Log::debug('Could not check build counter, but thats ok.'); + Log::warning($e->getMessage()); } try { @@ -224,8 +226,8 @@ class DebugController extends Controller $return['build_date'] = trim(file_get_contents('/var/www/build-date-main.txt')); } } catch (Exception $e) { - app('log')->debug('Could not check build date, but thats ok.'); - app('log')->warning($e->getMessage()); + Log::debug('Could not check build date, but thats ok.'); + Log::warning($e->getMessage()); } if ('' !== (string) env('BASE_IMAGE_BUILD')) { // @phpstan-ignore-line $return['base_build'] = env('BASE_IMAGE_BUILD'); // @phpstan-ignore-line @@ -282,7 +284,7 @@ class DebugController extends Controller $parts = Steam::getLocaleArray(Steam::getLocale()); foreach ($parts as $code) { $code = trim($code); - app('log')->debug(sprintf('Trying to set %s', $code)); + Log::debug(sprintf('Trying to set %s', $code)); $result = setlocale(LC_ALL, $code); $localeAttempts[$code] = $result === $code; } diff --git a/app/Http/Controllers/JavascriptController.php b/app/Http/Controllers/JavascriptController.php index c5beecfeb4..059e005eb9 100644 --- a/app/Http/Controllers/JavascriptController.php +++ b/app/Http/Controllers/JavascriptController.php @@ -30,6 +30,7 @@ use FireflyIII\Models\Account; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Http\Controllers\GetConfigurationData; use Illuminate\Http\Request; use Illuminate\Http\Response; @@ -114,6 +115,7 @@ class JavascriptController extends Controller 'currencyCode' => $currency->code, 'currencySymbol' => $currency->symbol, 'accountingLocaleInfo' => $accounting, + 'anonymous' => var_export(Steam::anonymous(), true), 'language' => $lang, 'dateRangeTitle' => $dateRange['title'], 'locale' => $locale, diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index d202a80af0..04d6a6b108 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -38,6 +38,7 @@ use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Http\Controllers\DateCalculation; use Illuminate\Http\JsonResponse; +use Illuminate\Support\Facades\Log; /** * Class BoxController. @@ -165,7 +166,7 @@ class BoxController extends Controller $allAccounts = $accountRepository->getActiveAccountsByType( [AccountTypeEnum::DEFAULT->value, AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value] ); - app('log')->debug(sprintf('Found %d accounts.', $allAccounts->count())); + Log::debug(sprintf('Found %d accounts.', $allAccounts->count())); // filter list on preference of being included. $filtered = $allAccounts->filter( @@ -173,7 +174,7 @@ class BoxController extends Controller $includeNetWorth = $accountRepository->getMetaValue($account, 'include_net_worth'); $result = null === $includeNetWorth || '1' === $includeNetWorth; if (false === $result) { - app('log')->debug(sprintf('Will not include "%s" in net worth charts.', $account->name)); + Log::debug(sprintf('Will not include "%s" in net worth charts.', $account->name)); } return $result; diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php index 3d88bb0a88..ed85b21885 100644 --- a/app/Http/Controllers/Json/ReconcileController.php +++ b/app/Http/Controllers/Json/ReconcileController.php @@ -81,6 +81,12 @@ class ReconcileController extends Controller if (!$start instanceof Carbon && !$end instanceof Carbon) { throw new FireflyException('Invalid dates submitted.'); } + if (!is_numeric($startBalance)) { + $startBalance = '0'; + } + if (!is_numeric($endBalance)) { + $endBalance = '0'; + } if ($end->lt($start)) { [$start, $end] = [$end, $start]; } @@ -197,10 +203,16 @@ class ReconcileController extends Controller $currency = $this->accountRepos->getAccountCurrency($account) ?? $this->primaryCurrency; // correct - Log::debug(sprintf('transactions: Call finalAccountBalance with date/time "%s"', $startDate->toIso8601String())); - Log::debug(sprintf('transactions2: Call finalAccountBalance with date/time "%s"', $end->toIso8601String())); - $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); - $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); + Log::debug(sprintf('transactions: Call accountsBalancesOptimized with date/time "%s"', $startDate->toIso8601String())); + Log::debug(sprintf('transactions2: Call accountsBalancesOptimized with date/time "%s"', $end->toIso8601String())); + + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized + // $startBalance = Steam::bcround(Steam::finalAccountBalance($account, $startDate)['balance'], $currency->decimal_places); + // $endBalance = Steam::bcround(Steam::finalAccountBalance($account, $end)['balance'], $currency->decimal_places); + + $startBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $startDate)[$account->id]; + $endBalance = Steam::accountsBalancesOptimized(new Collection()->push($account), $end)[$account->id]; + // get the transactions $selectionStart = clone $start; diff --git a/app/Http/Controllers/Rule/CreateController.php b/app/Http/Controllers/Rule/CreateController.php index 24c9c8bd64..5b95005a1b 100644 --- a/app/Http/Controllers/Rule/CreateController.php +++ b/app/Http/Controllers/Rule/CreateController.php @@ -268,7 +268,6 @@ class CreateController extends Controller public function store(RuleFormRequest $request) { $data = $request->getRuleData(); - $rule = $this->ruleRepos->store($data); session()->flash('success_url', route('rules.select-transactions', [$rule->id])); session()->flash('success', (string) trans('firefly.stored_new_rule', ['title' => $rule->title])); @@ -283,6 +282,9 @@ class CreateController extends Controller if ((int) $request->get('bill_id') > 0) { return redirect($this->getPreviousUrl('bills.create.url')); } + if (true === $data['run_after_form']) { + return redirect(route('rules.select-transactions', [$rule->id])); + } $redirect = redirect($this->getPreviousUrl('rules.create.url')); diff --git a/app/Http/Controllers/Rule/EditController.php b/app/Http/Controllers/Rule/EditController.php index f1888c425d..b9295d5487 100644 --- a/app/Http/Controllers/Rule/EditController.php +++ b/app/Http/Controllers/Rule/EditController.php @@ -205,6 +205,11 @@ class EditController extends Controller session()->flash('success', (string) trans('firefly.updated_rule', ['title' => $rule->title])); app('preferences')->mark(); $redirect = redirect($this->getPreviousUrl('rules.edit.url')); + + if (true === $data['run_after_form']) { + return redirect(route('rules.select-transactions', [$rule->id])); + } + if (1 === (int) $request->get('return_to_edit')) { session()->put('rules.edit.fromUpdate', true); diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index 1084a9f2c8..458d866378 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Rule; +use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SelectTransactionsRequest; @@ -56,7 +57,7 @@ class SelectController extends Controller $this->middleware( static function ($request, $next) { - app('view')->share('title', (string) trans('firefly.rules')); + app('view')->share('title', (string)trans('firefly.rules')); app('view')->share('mainTitleIcon', 'fa-random'); return $next($request); @@ -73,11 +74,20 @@ class SelectController extends Controller /** @var User $user */ $user = auth()->user(); $accounts = implode(',', $request->get('accounts')); - // create new rule engine: $newRuleEngine = app(RuleEngineInterface::class); $newRuleEngine->setUser($user); + // add date operators. + if (null !== $request->get('start')) { + $startDate = new Carbon($request->get('start')); + $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); + } + if (null !== $request->get('end')) { + $endDate = new Carbon($request->get('end')); + $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); + } + // add extra operators: $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); @@ -102,7 +112,7 @@ class SelectController extends Controller return redirect(route('rules.index')); } // does the user have shared accounts? - $subTitle = (string) trans('firefly.apply_rule_selection', ['title' => $rule->title]); + $subTitle = (string)trans('firefly.apply_rule_selection', ['title' => $rule->title]); return view('rules.rule.select-transactions', compact('rule', 'subTitle')); } @@ -127,7 +137,7 @@ class SelectController extends Controller // warn if nothing. if (0 === count($textTriggers)) { - return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); + return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); } foreach ($textTriggers as $textTrigger) { @@ -160,7 +170,7 @@ class SelectController extends Controller // Warn the user if only a subset of transactions is returned $warning = ''; if (0 === count($collection)) { - $warning = (string) trans('firefly.warning_no_matching_transactions'); + $warning = (string)trans('firefly.warning_no_matching_transactions'); } // Return json response @@ -190,7 +200,7 @@ class SelectController extends Controller $triggers = $rule->ruleTriggers; if (0 === count($triggers)) { - return response()->json(['html' => '', 'warning' => (string) trans('firefly.warning_no_valid_triggers')]); + return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); } // create new rule engine: $newRuleEngine = app(RuleEngineInterface::class); @@ -202,7 +212,7 @@ class SelectController extends Controller $warning = ''; if (0 === count($collection)) { - $warning = (string) trans('firefly.warning_no_matching_transactions'); + $warning = (string)trans('firefly.warning_no_matching_transactions'); } // Return json response diff --git a/app/Http/Controllers/RuleGroup/ExecutionController.php b/app/Http/Controllers/RuleGroup/ExecutionController.php index a0191cba3f..14ee17209a 100644 --- a/app/Http/Controllers/RuleGroup/ExecutionController.php +++ b/app/Http/Controllers/RuleGroup/ExecutionController.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\RuleGroup; +use Carbon\Carbon; use Exception; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SelectTransactionsRequest; @@ -73,6 +74,16 @@ class ExecutionController extends Controller $newRuleEngine = app(RuleEngineInterface::class); $newRuleEngine->setUser($user); + // add date operators. + if (null !== $request->get('start')) { + $startDate = new Carbon($request->get('start')); + $newRuleEngine->addOperator(['type' => 'date_after', 'value' => $startDate->format('Y-m-d')]); + } + if (null !== $request->get('end')) { + $endDate = new Carbon($request->get('end')); + $newRuleEngine->addOperator(['type' => 'date_before', 'value' => $endDate->format('Y-m-d')]); + } + // add extra operators: $newRuleEngine->addOperator(['type' => 'account_id', 'value' => $accounts]); diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 6e1e168d0a..f07f47758b 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -34,6 +34,7 @@ use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use Laravel\Passport\Passport; use phpseclib3\Crypt\RSA; @@ -81,8 +82,10 @@ class InstallController extends Controller public function index() { app('view')->share('FF_VERSION', config('firefly.version')); + // index will set FF3 version. FireflyConfig::set('ff3_version', (string) config('firefly.version')); + FireflyConfig::set('ff3_build_time', (int) config('firefly.build_time')); return view('install.index'); } @@ -98,18 +101,18 @@ class InstallController extends Controller 'errorMessage' => null, ]; - app('log')->debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); + Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); $indexes = array_keys($this->upgradeCommands); if (array_key_exists($requestIndex, $indexes)) { $command = $indexes[$requestIndex]; $parameters = $this->upgradeCommands[$command]; - app('log')->debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters); + Log::debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters); try { $result = $this->executeCommand($command, $parameters); } catch (FireflyException $e) { - app('log')->error($e->getMessage()); - app('log')->error($e->getTraceAsString()); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); if (str_contains($e->getMessage(), 'open_basedir restriction in effect')) { $this->lastError = self::BASEDIR_ERROR; } @@ -134,7 +137,7 @@ class InstallController extends Controller */ private function executeCommand(string $command, array $args): bool { - app('log')->debug(sprintf('Will now call command %s with args.', $command), $args); + Log::debug(sprintf('Will now call command %s with args.', $command), $args); try { if ('generate-keys' === $command) { @@ -142,7 +145,7 @@ class InstallController extends Controller } if ('generate-keys' !== $command) { Artisan::call($command, $args); - app('log')->debug(Artisan::output()); + Log::debug(Artisan::output()); } } catch (Exception $e) { // intentional generic exception throw new FireflyException($e->getMessage(), 0, $e); diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index bb5aa6f7d6..5b26839211 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -37,6 +37,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Update\JournalUpdateService; +use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Http\Controllers\ModelInformation; use FireflyIII\Transformers\TransactionGroupTransformer; @@ -45,6 +46,7 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Illuminate\Routing\Redirector; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Illuminate\View\View; @@ -228,11 +230,13 @@ class ConvertController extends Controller foreach ($accountList as $account) { $date = today()->endOfDay(); Log::debug(sprintf('getLiabilities: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $balance = Steam::finalAccountBalance($account, $date)['balance']; + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + // $balance = Steam::finalAccountBalance($account, $date)['balance']; + $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0'; $currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; - $role = 'l_'.$account->accountType->type; - $key = (string) trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; + $role = sprintf('l_%s', $account->accountType->type); + $key = (string) trans(sprintf('firefly.opt_group_%s', $role)); + $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false)); } return $grouped; @@ -252,15 +256,18 @@ class ConvertController extends Controller foreach ($accountList as $account) { $date = today()->endOfDay(); Log::debug(sprintf('getAssetAccounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $balance = Steam::finalAccountBalance($account, $date)['balance']; + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + // $balance = Steam::finalAccountBalance($account, $date)['balance']; + $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance'] ?? '0'; + $currency = $this->accountRepository->getAccountCurrency($account) ?? $this->primaryCurrency; $role = (string) $this->accountRepository->getMetaValue($account, 'account_role'); if ('' === $role) { $role = 'no_account_type'; } - $key = (string) trans('firefly.opt_group_'.$role); - $grouped[$key][$account->id] = $account->name.' ('.app('amount')->formatAnything($currency, $balance, false).')'; + $key = (string) trans(sprintf('firefly.opt_group_%s', $role)); + $grouped[$key][$account->id] = sprintf('%s (%s)', $account->name, Amount::formatAnything($currency, $balance, false)); } return $grouped; diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 8b7d9ca061..9cb1877ce9 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -54,6 +54,7 @@ class RuleFormRequest extends FormRequest 'description' => $this->stringWithNewlines('description'), 'stop_processing' => $this->boolean('stop_processing'), 'strict' => $this->boolean('strict'), + 'run_after_form' => $this->boolean('run_after_form'), 'triggers' => $this->getRuleTriggerData(), 'actions' => $this->getRuleActionData(), ]; @@ -140,16 +141,17 @@ class RuleFormRequest extends FormRequest // initial set of rules: $rules = [ - 'title' => 'required|min:1|max:255|uniqueObjectForUser:rules,title', - 'description' => 'min:1|max:32768|nullable', - 'stop_processing' => 'boolean', - 'rule_group_id' => 'required|belongsToUser:rule_groups', - 'trigger' => 'required|in:store-journal,update-journal,manual-activation', - 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers), - 'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers), - 'actions.*.type' => 'required|in:'.implode(',', $validActions), - 'actions.*.value' => [sprintf('required_if:actions.*.type,%s|min:0|max:1024', $contextActions), new IsValidActionExpression(), 'ruleActionValue'], - 'strict' => 'in:0,1', + 'title' => 'required|min:1|max:255|uniqueObjectForUser:rules,title', + 'description' => 'min:1|max:32768|nullable', + 'stop_processing' => 'boolean', + 'rule_group_id' => 'required|belongsToUser:rule_groups', + 'trigger' => 'required|in:store-journal,update-journal,manual-activation', + 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers), + 'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers), + 'actions.*.type' => 'required|in:'.implode(',', $validActions), + 'actions.*.value' => [sprintf('required_if:actions.*.type,%s|min:0|max:1024', $contextActions), new IsValidActionExpression(), 'ruleActionValue'], + 'strict' => 'in:0,1', + 'run_after_form' => 'in:0,1', ]; /** @var null|Rule $rule */ diff --git a/app/Jobs/MailError.php b/app/Jobs/MailError.php index ecc8c24b5d..6420ac67fd 100644 --- a/app/Jobs/MailError.php +++ b/app/Jobs/MailError.php @@ -31,6 +31,8 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Mail; +use Safe\Exceptions\FilesystemException; +use Safe\Exceptions\JsonException; use Symfony\Component\Mailer\Exception\TransportException; use function Safe\file_get_contents; @@ -54,7 +56,7 @@ class MailError extends Job implements ShouldQueue $debug = $this->exception; unset($debug['stackTrace'], $debug['headers']); - app('log')->error(sprintf('Exception is: %s', json_encode($debug))); + Log::error(sprintf('Exception is: %s', json_encode($debug))); } /** @@ -90,17 +92,17 @@ class MailError extends Job implements ShouldQueue } catch (Exception|TransportException $e) { $message = $e->getMessage(); if (str_contains($message, 'Bcc')) { - app('log')->warning('[Bcc] Could not email or log the error. Please validate your email settings, use the .env.example file as a guide.'); + Log::warning('[Bcc] Could not email or log the error. Please validate your email settings, use the .env.example file as a guide.'); return; } if (str_contains($message, 'RFC 2822')) { - app('log')->warning('[RFC] Could not email or log the error. Please validate your email settings, use the .env.example file as a guide.'); + Log::warning('[RFC] Could not email or log the error. Please validate your email settings, use the .env.example file as a guide.'); return; } - app('log')->error($e->getMessage()); - app('log')->error($e->getTraceAsString()); + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); } } } @@ -125,11 +127,25 @@ class MailError extends Job implements ShouldQueue if (!file_exists($file)) { Log::debug(sprintf('Wrote new file in "%s"', $file)); - file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); + + try { + file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); + } catch (FilesystemException $e) { + Log::warning(sprintf('[a] Could not write file "%s": %s', $file, $e->getMessage())); + } catch (JsonException $e) { + Log::warning(sprintf('[b] Could not parse file "%s": %s', $file, $e->getMessage())); + } } if (file_exists($file)) { Log::debug(sprintf('Read file in "%s"', $file)); - $limits = json_decode(file_get_contents($file), true); + + try { + $limits = json_decode(file_get_contents($file), true); + } catch (FilesystemException $e) { + Log::warning(sprintf('[c] Could not read file "%s": %s', $file, $e->getMessage())); + } catch (JsonException $e) { + Log::warning(sprintf('[d] Could not parse file "%s": %s', $file, $e->getMessage())); + } } // limit reached? foreach ($types as $type => $info) { @@ -157,7 +173,14 @@ class MailError extends Job implements ShouldQueue } ++$limits[$type]['sent']; } - file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); + + try { + file_put_contents($file, json_encode($limits, JSON_PRETTY_PRINT)); + } catch (FilesystemException $e) { + Log::warning(sprintf('[c] Could not write file "%s": %s', $file, $e->getMessage())); + } catch (JsonException $e) { + Log::warning(sprintf('[c] Could not parse file "%s": %s', $file, $e->getMessage())); + } Log::debug('No limits reached, return FALSE.'); return false; diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php index 63260b6643..4278ac4183 100644 --- a/app/Repositories/Account/AccountTasker.php +++ b/app/Repositories/Account/AccountTasker.php @@ -50,10 +50,11 @@ class AccountTasker implements AccountTaskerInterface, UserGroupInterface $yesterday = clone $start; $yesterday->subDay()->endOfDay(); // exactly up until $start but NOT including. $end->endOfDay(); // needs to be end of day to be correct. - Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $yesterday->format('Y-m-d H:i:s'))); - Log::debug(sprintf('getAccountReport: accountsBalancesOptimized("%s")', $end->format('Y-m-d H:i:s'))); - $startSet = Steam::accountsBalancesOptimized($accounts, $yesterday); - $endSet = Steam::accountsBalancesOptimized($accounts, $end); + Log::debug(sprintf('getAccountReport: accountsBalancesInRange("%s", "%s")', $yesterday->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + [ + $startSet, + $endSet, + ] = Steam::accountsBalancesInRange($accounts, $yesterday, $end); Log::debug('Start of accountreport'); /** @var AccountRepositoryInterface $repository */ diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index 056825e7d8..022de3a98c 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -375,7 +375,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface, UserGroupInte { Log::debug(sprintf('leftOnAccount("%s","%s","%s")', $piggyBank->name, $account->name, $date->format('Y-m-d H:i:s'))); Log::debug(sprintf('leftOnAccount: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $balance = Steam::finalAccountBalance($account, $date)['balance']; + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + $balance = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]['balance']; + // $balance = Steam::finalAccountBalance($account, $date)['balance']; Log::debug(sprintf('Balance is: %s', $balance)); diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index a3256ccf50..8ef670d2b2 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -139,7 +139,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte /** @var Rule $entry */ foreach ($set as $entry) { if ($entry->order !== $count) { - app('log')->debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); + Log::debug(sprintf('Rule #%d was on spot %d but must be on spot %d', $entry->id, $entry->order, $count)); $entry->order = $count; $entry->save(); } @@ -167,7 +167,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte if ($action->order !== $index) { $action->order = $index; $action->save(); - app('log')->debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); + Log::debug(sprintf('Rule action #%d was on spot %d but must be on spot %d', $action->id, $action->order, $index)); } ++$index; } @@ -189,7 +189,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte if ($order !== $index) { $trigger->order = $index; $trigger->save(); - app('log')->debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); + Log::debug(sprintf('Rule trigger #%d was on spot %d but must be on spot %d', $trigger->id, $order, $index)); } ++$index; } @@ -235,6 +235,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte public function getActiveStoreRules(RuleGroup $group): Collection { return $group->rules() + ->orderBy('rules.order', 'ASC') ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') ->where('rule_triggers.trigger_type', 'user_action') ->where('rule_triggers.trigger_value', 'store-journal') @@ -262,6 +263,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte [ // @phpstan-ignore-line 'rules' => static function (HasMany $query): void { $query->orderBy('order', 'ASC'); + $query->where('rules.active', true); }, 'rules.ruleTriggers' => static function (HasMany $query): void { $query->orderBy('order', 'ASC'); @@ -275,23 +277,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte if (null === $filter) { return $groups; } - app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); + Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); return $groups->map( static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line - app('log')->debug(sprintf('Now filtering group #%d', $group->id)); + Log::debug(sprintf('Now filtering group #%d', $group->id)); // filter the rules in the rule group: $group->rules = $group->rules->filter( static function (Rule $rule) use ($filter) { - app('log')->debug(sprintf('Now filtering rule #%d', $rule->id)); + Log::debug(sprintf('Now filtering rule #%d', $rule->id)); foreach ($rule->ruleTriggers as $trigger) { if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { - app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); + Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); return true; } } - app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); + Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); return false; } @@ -318,6 +320,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte [ // @phpstan-ignore-line 'rules' => static function (HasMany $query): void { $query->orderBy('order', 'ASC'); + $query->where('rules.active', true); }, 'rules.ruleTriggers' => static function (HasMany $query): void { $query->orderBy('order', 'ASC'); @@ -331,23 +334,23 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte if (null === $filter) { return $groups; } - app('log')->debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); + Log::debug(sprintf('Will filter getRuleGroupsWithRules on "%s".', $filter)); return $groups->map( static function (RuleGroup $group) use ($filter) { // @phpstan-ignore-line - app('log')->debug(sprintf('Now filtering group #%d', $group->id)); + Log::debug(sprintf('Now filtering group #%d', $group->id)); // filter the rules in the rule group: $group->rules = $group->rules->filter( static function (Rule $rule) use ($filter) { - app('log')->debug(sprintf('Now filtering rule #%d', $rule->id)); + Log::debug(sprintf('Now filtering rule #%d', $rule->id)); foreach ($rule->ruleTriggers as $trigger) { if ('user_action' === $trigger->trigger_type && $filter === $trigger->trigger_value) { - app('log')->debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); + Log::debug(sprintf('Rule #%d triggers on %s, include it.', $rule->id, $filter)); return true; } } - app('log')->debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); + Log::debug(sprintf('Rule #%d does not trigger on %s, do not include it.', $rule->id, $filter)); return false; } @@ -414,7 +417,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte ->decrement('order') ; $ruleGroup->order = $newOrder; - app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); + Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); $ruleGroup->save(); return; @@ -425,7 +428,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface, UserGroupInte ->increment('order') ; $ruleGroup->order = $newOrder; - app('log')->debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); + Log::debug(sprintf('Order of group #%d ("%s") is now %d', $ruleGroup->id, $ruleGroup->title, $newOrder)); $ruleGroup->save(); } diff --git a/app/Rules/Account/IsValidAccountTypeList.php b/app/Rules/Account/IsValidAccountTypeList.php new file mode 100644 index 0000000000..8f5b7422f2 --- /dev/null +++ b/app/Rules/Account/IsValidAccountTypeList.php @@ -0,0 +1,56 @@ +translate(); + } + $keys = array_keys($this->types); + foreach ($values as $entry) { + $entry = (string) $entry; + if (!in_array($entry, $keys, true)) { + $fail('validation.invalid_account_list')->translate(); + } + } + } +} diff --git a/app/Rules/IsValidSortInstruction.php b/app/Rules/IsValidSortInstruction.php index 4d9e7b0cf7..66ed508fc3 100644 --- a/app/Rules/IsValidSortInstruction.php +++ b/app/Rules/IsValidSortInstruction.php @@ -38,6 +38,11 @@ class IsValidSortInstruction implements ValidationRule return; } + if ('' === $value) { + // don't validate. + + return; + } $validParameters = config(sprintf('firefly.allowed_sort_parameters.%s', $shortClass)); if (!is_array($validParameters)) { $fail('validation.no_sort_instructions')->translate(['object' => $shortClass]); diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index a4ce044ebf..ec9a7dec3e 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -88,6 +88,7 @@ trait JournalServiceTrait // the account that Firefly III creates must be "creatable", aka select the one we can create from the list just in case $creatableType = $this->getCreatableType($expectedTypes[$transactionType]); + Log::debug(sprintf('Creatable type is "%s"', $creatableType), $expectedTypes[$transactionType]); // if the result is NULL but the ID is set, an account could exist of the wrong type. // that data can be used to create a new account of the right type. @@ -227,9 +228,11 @@ trait JournalServiceTrait } // find by preferred type. + Log::debug('Find by preferred type.'); $result = $this->accountRepository->findByName($data['name'], [$types[0]]); // or any expected type. + Log::debug('Find by any expected type.'); $result ??= $this->accountRepository->findByName($data['name'], $types); if (null !== $result) { diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 98e8aa284c..f4bf1de35c 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -163,6 +163,7 @@ class Amount */ public function formatFlat(string $symbol, int $decimalPlaces, string $amount, ?bool $coloured = null): string { + $amount = Steam::anonymous() ? '0' : $amount; $locale = Steam::getLocale(); $rounded = Steam::bcround($amount, $decimalPlaces); $coloured ??= true; diff --git a/app/Support/Authentication/RemoteUserGuard.php b/app/Support/Authentication/RemoteUserGuard.php index 1c87806e00..f53ecaa4fc 100644 --- a/app/Support/Authentication/RemoteUserGuard.php +++ b/app/Support/Authentication/RemoteUserGuard.php @@ -49,14 +49,14 @@ class RemoteUserGuard implements Guard { /** @var null|Request $request */ $request = $app->get('request'); - Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri())); + // Log::debug(sprintf('Created RemoteUserGuard for %s "%s"', $request?->getMethod(), $request?->getRequestUri())); $this->application = $app; $this->user = null; } public function authenticate(): void { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); if ($this->user instanceof User) { Log::debug(sprintf('%s is found: #%d, "%s".', $this->user::class, $this->user->id, $this->user->email)); @@ -104,21 +104,21 @@ class RemoteUserGuard implements Guard public function check(): bool { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); return $this->user() instanceof User; } public function guest(): bool { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); return !$this->check(); } public function hasUser(): bool { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); throw new FireflyException('Did not implement RemoteUserGuard::hasUser()'); } @@ -128,14 +128,14 @@ class RemoteUserGuard implements Guard */ public function id(): int|string|null { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); return $this->user?->id; } public function setUser(Authenticatable|User|null $user): void // @phpstan-ignore-line { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); if ($user instanceof User) { $this->user = $user; @@ -146,7 +146,7 @@ class RemoteUserGuard implements Guard public function user(): ?User { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); $user = $this->user; if (!$user instanceof User) { Log::debug('User is NULL'); @@ -164,14 +164,14 @@ class RemoteUserGuard implements Guard */ public function validate(array $credentials = []): bool { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); throw new FireflyException('Did not implement RemoteUserGuard::validate()'); } public function viaRemember(): bool { - Log::debug(sprintf('Now at %s', __METHOD__)); + // Log::debug(sprintf('Now at %s', __METHOD__)); return false; } diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index caa114007b..6fec7eadda 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -634,10 +634,10 @@ trait PeriodOverview $currencySymbol = $journal['currency_symbol']; $currencyDecimalPlaces = $journal['currency_decimal_places']; $foreignCurrencyId = $journal['foreign_currency_id']; - $amount = $journal['amount'] ?? '0'; + $amount = (string) ($journal['amount'] ?? '0'); if ($this->convertToPrimary && $currencyId !== $this->primaryCurrency->id && $foreignCurrencyId !== $this->primaryCurrency->id) { - $amount = $journal['pc_amount'] ?? '0'; + $amount = (string) ($journal['pc_amount'] ?? '0'); $currencyId = $this->primaryCurrency->id; $currencyCode = $this->primaryCurrency->code; $currencyName = $this->primaryCurrency->name; @@ -650,7 +650,7 @@ trait PeriodOverview $currencyName = $journal['foreign_currency_name']; $currencySymbol = $journal['foreign_currency_symbol']; $currencyDecimalPlaces = $journal['foreign_currency_decimal_places']; - $amount = $journal['foreign_amount'] ?? '0'; + $amount = (string) ($journal['foreign_amount'] ?? '0'); } $return[$currencyId] ??= [ 'amount' => '0', diff --git a/app/Support/JsonApi/Enrichments/AccountEnrichment.php b/app/Support/JsonApi/Enrichments/AccountEnrichment.php index 7aedeb6880..43179333d6 100644 --- a/app/Support/JsonApi/Enrichments/AccountEnrichment.php +++ b/app/Support/JsonApi/Enrichments/AccountEnrichment.php @@ -289,8 +289,10 @@ class AccountEnrichment implements EnrichmentInterface { $this->balances = Steam::accountsBalancesOptimized($this->collection, $this->getDate(), $this->primaryCurrency, $this->convertToPrimary); if ($this->start instanceof Carbon && $this->end instanceof Carbon) { - $this->startBalances = Steam::accountsBalancesOptimized($this->collection, $this->start, $this->primaryCurrency, $this->convertToPrimary); - $this->endBalances = Steam::accountsBalancesOptimized($this->collection, $this->end, $this->primaryCurrency, $this->convertToPrimary); + [ + $this->startBalances, + $this->endBalances, + ] = Steam::accountsBalancesInRange($this->collection, $this->start, $this->end, $this->primaryCurrency, $this->convertToPrimary); } } diff --git a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php index e1a6be6a94..8306274b4d 100644 --- a/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php +++ b/app/Support/JsonApi/Enrichments/BudgetLimitEnrichment.php @@ -154,6 +154,9 @@ class BudgetLimitEnrichment implements EnrichmentInterface $this->start = $this->collection->min('start_date') ?? Carbon::now()->startOfMonth(); $this->end = $this->collection->max('end_date') ?? Carbon::now()->endOfMonth(); + // #11096 make sure that the max end date is also at the end of the day, + $this->end->endOfDay(); + /** @var BudgetLimit $limit */ foreach ($this->collection as $limit) { $id = (int)$limit->id; diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index dd5981e327..b80548d990 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -420,6 +420,7 @@ class Navigation 'week' => (string)trans('config.week_in_year_js'), 'weekly' => (string)trans('config.week_in_year_js'), '1M' => (string)trans('config.month_js'), + 'MTD' => (string)trans('config.month_js'), 'month' => (string)trans('config.month_js'), 'monthly' => (string)trans('config.month_js'), '1Y' => (string)trans('config.year_js'), @@ -427,6 +428,11 @@ class Navigation 'year' => (string)trans('config.year_js'), 'yearly' => (string)trans('config.year_js'), '6M' => (string)trans('config.half_year_js'), + 'last7' => (string)trans('config.specific_day_js'), + 'last30' => (string)trans('config.month_js'), + 'last90' => (string)trans('config.month_js'), + 'last365' => (string)trans('config.year_js'), + 'QTD' => (string)trans('config.month_js'), ]; if (array_key_exists($repeatFrequency, $formatMap)) { diff --git a/app/Support/Repositories/UserGroup/UserGroupTrait.php b/app/Support/Repositories/UserGroup/UserGroupTrait.php index 98781e5596..6a4a958917 100644 --- a/app/Support/Repositories/UserGroup/UserGroupTrait.php +++ b/app/Support/Repositories/UserGroup/UserGroupTrait.php @@ -88,7 +88,7 @@ trait UserGroupTrait public function setUserGroup(UserGroup $userGroup): void { if (null === $this->user) { - Log::warning(sprintf('User is not set in repository %s', static::class)); + Log::warning(sprintf('User is not set in repository %s. This does not have to be a problem.', static::class)); } $this->userGroup = $userGroup; } diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index a57c45a320..f8cb76378f 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -402,21 +402,21 @@ trait ConvertsDataTypes */ protected function getCarbonDate(string $field): ?Carbon { - $result = null; + $data = (string)$this->get($field); + Log::debug(sprintf('Date string is "%s"', $data)); - Log::debug(sprintf('Date string is "%s"', (string)$this->get($field))); + if ('' === $data) { + return null; + } try { - $result = '' !== (string)$this->get($field) ? new Carbon((string)$this->get($field), config('app.timezone')) : null; + return new Carbon($data, config('app.timezone')); } catch (InvalidFormatException) { // @ignoreException - Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); - } - if (!$result instanceof Carbon) { - Log::debug(sprintf('Exception when parsing date "%s".', $this->get($field))); + Log::debug(sprintf('Exception when parsing date "%s".', $data)); } - return $result; + return null; } /** diff --git a/app/Support/Search/QueryParser/QueryParser.php b/app/Support/Search/QueryParser/QueryParser.php index bef27a0ec9..e9d5b7c914 100644 --- a/app/Support/Search/QueryParser/QueryParser.php +++ b/app/Support/Search/QueryParser/QueryParser.php @@ -52,13 +52,23 @@ class QueryParser implements QueryParserInterface $inQuotes = false; $fieldName = ''; $prohibited = false; - - while ($this->position < strlen($this->query)) { - $char = $this->query[$this->position]; + $chrArray = preg_split('//u', $this->query, -1, PREG_SPLIT_NO_EMPTY); + $count = count($chrArray); + while ($this->position < $count) { + $char = $chrArray[$this->position]; + $nextChar = $chrArray[$this->position + 1] ?? ''; // Log::debug(sprintf('Char #%d: %s', $this->position, $char)); // If we're in a quoted string, we treat all characters except another quote as ordinary characters if ($inQuotes) { + if ('\\' === $char && '"' === $nextChar) { + // Log::debug('BACKSLASH!'); + // escaped quote, pretend it's a normal char and continue two places (skipping the actual character). + $tokenUnderConstruction .= '\\'.$nextChar; + $this->position += 2; + + continue; + } if ('"' !== $char) { $tokenUnderConstruction .= $char; ++$this->position; @@ -67,11 +77,9 @@ class QueryParser implements QueryParserInterface } // char is " ++$this->position; + Log::debug(sprintf('Constructed token: %s', $tokenUnderConstruction)); - return new NodeResult( - $this->createNode($tokenUnderConstruction, $fieldName, $prohibited), - false - ); + return new NodeResult($this->createNode($tokenUnderConstruction, $fieldName, $prohibited), false); } switch ($char) { @@ -197,9 +205,13 @@ class QueryParser implements QueryParserInterface private function createNode(string $token, string $fieldName, bool $prohibited): Node { if ('' !== $fieldName) { - Log::debug(sprintf('Create FieldNode %s:%s (%s)', $fieldName, $token, var_export($prohibited, true))); + // OK dus hoe trim je \" correct? $token = ltrim($token, ':"'); - $token = rtrim($token, '"'); + if (!str_ends_with($token, '\"')) { + $token = rtrim($token, '"'); + } + $token = str_replace('\"', '"', $token); + Log::debug(sprintf('Create FieldNode %s:%s (%s)', $fieldName, $token, var_export($prohibited, true))); return new FieldNode(trim($fieldName), trim($token), $prohibited); } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 10a8061153..e290941e87 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -49,9 +49,9 @@ use function Safe\preg_replace; */ class Steam { - public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array + public function accountsBalancesOptimized(Collection $accounts, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array { - Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s"', $accounts->count(), $date->toIso8601String())); + Log::debug(sprintf('accountsBalancesOptimized: Called for %d account(s) with date/time "%s" (inclusive: %s)', $accounts->count(), $date->toIso8601String(), var_export($inclusive, true))); $result = []; $convertToPrimary ??= Amount::convertToPrimary(); $primary ??= Amount::getPrimaryCurrency(); @@ -61,14 +61,15 @@ class Steam $arrayOfSums = Transaction::whereIn('account_id', $accounts->pluck('id')->toArray()) ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) + ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) ->groupBy(['transactions.account_id', 'transaction_currencies.code']) ->get(['transactions.account_id', 'transaction_currencies.code', DB::raw('SUM(transactions.amount) as sum_of_amount')])->toArray() ; + Log::debug('Array of sums: ', $arrayOfSums); + /** @var Account $account */ foreach ($accounts as $account) { - // this array is PER account, so we wait a bit before we change code here. $return = [ 'pc_balance' => '0', 'balance' => '0', // this key is overwritten right away, but I must remember it is always created. @@ -76,19 +77,19 @@ class Steam $currency = $currencies[$account->id]; // second array - $accountSum = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id); - if (0 === count($accountSum)) { + $accountSums = array_filter($arrayOfSums, fn ($entry) => $entry['account_id'] === $account->id); + if (0 === count($accountSums)) { $result[$account->id] = $return; continue; } - $accountSum = array_values($accountSum)[0]; - $sumOfAmount = (string)$accountSum['sum_of_amount']; - $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount); - $sumsByCode = [ - $accountSum['code'] => $sumOfAmount, - ]; - + $sumsByCode = []; + foreach ($accountSums as $accountSum) { + // $accountSum = array_values($accountSum)[0]; + $sumOfAmount = (string)$accountSum['sum_of_amount']; + $sumOfAmount = $this->floatalize('' === $sumOfAmount ? '0' : $sumOfAmount); + $sumsByCode[$accountSum['code']] = $sumOfAmount; + } // Log::debug('All balances are (joined)', $others); // if there is no request to convert, take this as "balance" and "pc_balance". $return['balance'] = $sumsByCode[$currency->code] ?? '0'; @@ -96,6 +97,7 @@ class Steam unset($return['pc_balance']); // Log::debug(sprintf('Set balance to %s, unset pc_balance', $return['balance'])); } + // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency. if ($convertToPrimary) { $return['pc_balance'] = $this->convertAllBalances($sumsByCode, $primary, $date); @@ -119,12 +121,23 @@ class Steam } $final = array_merge($return, $sumsByCode); $result[$account->id] = $final; - // Log::debug('Final balance is', $final); + Log::debug(sprintf('Final balance for account #%d is', $account->id), $final); } return $result; } + /** + * Calls accountsBalancesOptimized for the given accounts and makes sure that inclusive is set to false, so it properly gets the balance of a range. + */ + public function accountsBalancesInRange(Collection $accounts, Carbon $start, Carbon $end, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array + { + return [ + $this->accountsBalancesOptimized($accounts, $start, $primary, $convertToPrimary, inclusive: false), + $this->accountsBalancesOptimized($accounts, $end, $primary, $convertToPrimary), + ]; + } + /** * https://stackoverflow.com/questions/1642614/how-to-ceil-floor-and-round-bcmath-numbers */ @@ -276,7 +289,9 @@ class Steam } /** - * Returns smaller than or equal to, so be careful with END OF DAY. + * @deprecated + * By default this method returns "smaller than or equal to", so be careful with END OF DAY. + * If you need end of day balance, use "inclusive = false". * * Returns the balance of an account at exact moment given. Array with at least one value. * Always returns: @@ -288,18 +303,17 @@ class Steam * --> "pc_balance": balance in the user's primary currency, with all amounts converted to the primary currency. * "EUR": balance in EUR (or whatever currencies the account has balance in) */ - public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null): array + public function finalAccountBalance(Account $account, Carbon $date, ?TransactionCurrency $primary = null, ?bool $convertToPrimary = null, bool $inclusive = true): array { $cache = new CacheProperties(); $cache->addProperty($account->id); $cache->addProperty($date); if ($cache->has()) { - Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s'))); - + Log::debug(sprintf('CACHED finalAccountBalance(#%d, %s, inclusive:%s)', $account->id, $date->format('Y-m-d H:i:s'), var_export($inclusive, true))); // return $cache->get(); } - // Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s'))); + Log::debug(sprintf('finalAccountBalance(#%d, %s)', $account->id, $date->format('Y-m-d H:i:s'))); if (null === $convertToPrimary) { $convertToPrimary = Amount::convertToPrimary($account->user); } @@ -312,7 +326,6 @@ class Steam $accountCurrency = $account->meta['currency']; } if (!$currencyPresent) { - $accountCurrency = $this->getAccountCurrency($account); } $hasCurrency = null !== $accountCurrency; @@ -325,11 +338,11 @@ class Steam $array = $account->transactions() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') ->leftJoin('transaction_currencies', 'transaction_currencies.id', '=', 'transactions.transaction_currency_id') - ->where('transaction_journals.date', '<=', $date->format('Y-m-d H:i:s')) + ->where('transaction_journals.date', $inclusive ? '<=' : '<', $date->format('Y-m-d H:i:s')) ->get(['transaction_currencies.code', 'transactions.amount'])->toArray() ; $others = $this->groupAndSumTransactions($array, 'code', 'amount'); - // Log::debug('All balances are (joined)', $others); + Log::debug('All balances are (joined)', $others); // if there is no request to convert, take this as "balance" and "pc_balance". $return['balance'] = $others[$currency->code] ?? '0'; if (!$convertToPrimary) { @@ -338,7 +351,7 @@ class Steam } // if there is a request to convert, convert to "pc_balance" and use "balance" for whichever amount is in the primary currency. if ($convertToPrimary) { - $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // todo sum all and convert. + $return['pc_balance'] = $this->convertAllBalances($others, $primary, $date); // Log::debug(sprintf('Set pc_balance to %s', $return['pc_balance'])); } @@ -358,18 +371,21 @@ class Steam // Log::debug(sprintf('Virtual balance makes the (primary currency) total %s', $return['balance'])); } $final = array_merge($return, $others); - // Log::debug('Final balance is', $final); + Log::debug('Final balance is', $final); $cache->store($final); return $final; } + /** + * Returns the balance for the given account in the range, with daily precision. + */ public function finalAccountBalanceInRange(Account $account, Carbon $start, Carbon $end, bool $convertToPrimary): array { // expand period. $start->startOfDay(); $end->endOfDay(); - Log::debug(sprintf('finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); + Log::debug(sprintf('called finalAccountBalanceInRange(#%d, %s, %s)', $account->id, $start->format('Y-m-d H:i:s'), $end->format('Y-m-d H:i:s'))); // set up cache $cache = new CacheProperties(); @@ -379,28 +395,21 @@ class Steam $cache->addProperty($convertToPrimary); $cache->addProperty($end); if ($cache->has()) { - return $cache->get(); + Log::debug('Return cached finalAccountBalanceInRange'); + // return $cache->get(); } $balances = []; $formatted = $start->format('Y-m-d'); - /* - * To make sure the start balance is correct, we need to get the balance at the exact end of the previous day. - * Since we just did "startOfDay" we can do subDay()->endOfDay() to get the correct moment. - * THAT will be the start balance. - */ - $request = clone $start; - $request->subDay()->endOfDay(); Log::debug('Get first balance to start.'); - Log::debug(sprintf('finalAccountBalanceInRange: Call finalAccountBalance with date/time "%s"', $request->toIso8601String())); - $startBalance = $this->finalAccountBalance($account, $request); + // 2025-10-08 replaced finalAccountBalance with accountsBalancesOptimized: $primaryCurrency = Amount::getPrimaryCurrencyByUserGroup($account->user->userGroup); + $startBalance = $this->accountsBalancesOptimized(new Collection()->push($account), $start, $primaryCurrency, $convertToPrimary, false)[$account->id]; $accountCurrency = $this->getAccountCurrency($account); $hasCurrency = $accountCurrency instanceof TransactionCurrency; $currency = $accountCurrency ?? $primaryCurrency; Log::debug(sprintf('Currency is %s', $currency->code)); - // set start balances: $startBalance[$currency->code] ??= '0'; if ($hasCurrency) { @@ -542,7 +551,7 @@ class Steam try { $hostName = gethostbyaddr($ipAddress); } catch (Exception $e) { - app('log')->error($e->getMessage()); + Log::error($e->getMessage()); $hostName = $ipAddress; } @@ -618,6 +627,19 @@ class Steam return $locale; } + public function anonymous(): bool // get preference + { + $singleton = PreferencesSingleton::getInstance(); + $cached = $singleton->getPreference('anonymous'); + if (null !== $cached) { + return $cached; + } + $anonymous = app('preferences')->get('anonymous', config('firefly.default_preferences.anonymous', false))->data; + $singleton->setPreference('anonymous', $anonymous); + + return $anonymous; + } + public function getLocaleArray(string $locale): array { return [ @@ -757,7 +779,7 @@ class Steam $current = $converter->convert($currency, $primary, $date, $amount); Log::debug(sprintf('Convert %s %s to %s %s', $currency->code, $amount, $primary->code, $current)); } - $total = bcadd((string) $current, $total); + $total = bcadd((string)$current, $total); } return $total; diff --git a/app/Support/System/IsOldVersion.php b/app/Support/System/IsOldVersion.php index 2499debbe8..345463abfe 100644 --- a/app/Support/System/IsOldVersion.php +++ b/app/Support/System/IsOldVersion.php @@ -67,32 +67,16 @@ trait IsOldVersion protected function isOldVersionInstalled(): bool { // version compare thing. - $configVersion = (string)config('firefly.version'); - $dbVersion = (string)FireflyConfig::getFresh('ff3_version', '1.0')->data; - $compare = 0; - // compare develop to develop - if (str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) { - $compare = $this->compareDevelopVersions($configVersion, $dbVersion); - } - // user has develop installed, goes to normal version. - if (!str_starts_with($configVersion, 'develop') && str_starts_with($dbVersion, 'develop')) { - return true; - } - - // user has normal, goes to develop version. - if (str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) { - return true; - } - - // compare normal with normal. - if (!str_starts_with($configVersion, 'develop') && !str_starts_with($dbVersion, 'develop')) { - $compare = version_compare($configVersion, $dbVersion); - } - if (-1 === $compare) { - Log::warning(sprintf('The current configured Firefly III version (%s) is older than the required version (%s). Redirect to migrate routine.', $dbVersion, $configVersion)); + $configBuildTime = (int)config('firefly.build_time'); + $dbBuildTime = (int)FireflyConfig::getFresh('ff3_build_time', 123)->data; + $configTime = Carbon::createFromTimestamp($configBuildTime, config('app.timezone')); + $dbTime = Carbon::createFromTimestamp($dbBuildTime, config('app.timezone')); + if ($dbBuildTime < $configBuildTime) { + Log::warning(sprintf('Your database was last managed by an older version of Firefly III (I see %s, I expect %s). Redirect to migrate routine.', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'))); return true; } + Log::debug(sprintf('Your database is up to date (I see %s, I expect %s).', $dbTime->format('Y-m-d H:i:s'), $configTime->format('Y-m-d H:i:s'))); return false; } diff --git a/app/Support/System/OAuthKeys.php b/app/Support/System/OAuthKeys.php index afb028180a..0f66372a69 100644 --- a/app/Support/System/OAuthKeys.php +++ b/app/Support/System/OAuthKeys.php @@ -25,6 +25,7 @@ declare(strict_types=1); namespace FireflyIII\Support\System; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Support\Facades\FireflyConfig; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Crypt; @@ -63,10 +64,10 @@ class OAuthKeys $privateKey = ''; $publicKey = ''; // better check if keys are in the database: - if (app('fireflyconfig')->has(self::PRIVATE_KEY) && app('fireflyconfig')->has(self::PUBLIC_KEY)) { + if (FireflyConfig::has(self::PRIVATE_KEY) && FireflyConfig::has(self::PUBLIC_KEY)) { try { - $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data; - $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data; + $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data; + $publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data; } catch (ContainerExceptionInterface|FireflyException|NotFoundExceptionInterface $e) { app('log')->error(sprintf('Could not validate keysInDatabase(): %s', $e->getMessage())); app('log')->error($e->getTraceAsString()); @@ -87,8 +88,8 @@ class OAuthKeys */ public static function restoreKeysFromDB(): bool { - $privateKey = (string)app('fireflyconfig')->get(self::PRIVATE_KEY)?->data; - $publicKey = (string)app('fireflyconfig')->get(self::PUBLIC_KEY)?->data; + $privateKey = (string)FireflyConfig::get(self::PRIVATE_KEY)?->data; + $publicKey = (string)FireflyConfig::get(self::PUBLIC_KEY)?->data; try { $privateContent = Crypt::decrypt($privateKey); @@ -98,8 +99,8 @@ class OAuthKeys app('log')->error($e->getMessage()); // delete config vars from DB: - app('fireflyconfig')->delete(self::PRIVATE_KEY); - app('fireflyconfig')->delete(self::PUBLIC_KEY); + FireflyConfig::delete(self::PRIVATE_KEY); + FireflyConfig::delete(self::PUBLIC_KEY); return false; } @@ -115,8 +116,8 @@ class OAuthKeys { $private = storage_path('oauth-private.key'); $public = storage_path('oauth-public.key'); - app('fireflyconfig')->set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); - app('fireflyconfig')->set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); + FireflyConfig::set(self::PRIVATE_KEY, Crypt::encrypt(file_get_contents($private))); + FireflyConfig::set(self::PUBLIC_KEY, Crypt::encrypt(file_get_contents($public))); } public static function verifyKeysRoutine(): void diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 485dfe1bd9..80d72a9f75 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -30,6 +30,7 @@ use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Support\Facades\Amount; use FireflyIII\Support\Facades\Steam; use FireflyIII\Support\Search\OperatorQuerySearch; +use Illuminate\Support\Collection; use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Route; use League\CommonMark\GithubFlavoredMarkdownConverter; @@ -155,9 +156,12 @@ class General extends AbstractExtension } /** @var Carbon $date */ - $date = session('end', today(config('app.timezone'))->endOfMonth()); + $date = now(); Log::debug(sprintf('twig balance: Call finalAccountBalance with date/time "%s"', $date->toIso8601String())); - $info = Steam::finalAccountBalance($account, $date); + + // 2025-10-08 replace finalAccountBalance with accountsBalancesOptimized. + $info = Steam::accountsBalancesOptimized(new Collection()->push($account), $date)[$account->id]; + // $info = Steam::finalAccountBalance($account, $date); $currency = Steam::getAccountCurrency($account); $primary = Amount::getPrimaryCurrency(); $convertToPrimary = Amount::convertToPrimary(); diff --git a/app/TransactionRules/Actions/UpdatePiggyBank.php b/app/TransactionRules/Actions/UpdatePiggyBank.php index 9e21497d97..7a0bd2daaf 100644 --- a/app/TransactionRules/Actions/UpdatePiggyBank.php +++ b/app/TransactionRules/Actions/UpdatePiggyBank.php @@ -59,9 +59,7 @@ class UpdatePiggyBank implements ActionInterface $piggyBank = $this->findPiggyBank($user, $actionValue); if (!$piggyBank instanceof PiggyBank) { - Log::info( - sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id) - ); + Log::info(sprintf('No piggy bank named "%s", cant execute action #%d of rule #%d', $actionValue, $this->action->id, $this->action->rule_id)); event(new RuleActionFailedOnArray($this->action, $journal, trans('rules.cannot_find_piggy', ['name' => $actionValue]))); return false; diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php index 7843d1ffca..72f7228f20 100644 --- a/app/TransactionRules/Engine/SearchRuleEngine.php +++ b/app/TransactionRules/Engine/SearchRuleEngine.php @@ -506,9 +506,23 @@ class SearchRuleEngine implements RuleEngineInterface */ private function fireGroup(RuleGroup $group): void { - Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $group->rules->count())); - - $rules = $group->rules()->orderBy('order', 'ASC')->get(); + $rules = []; + if ($group->relationLoaded('rules')) { + Log::debug('Group rules have been pre-loaded, do not reload them.'); + $rules = $group->rules; + } + if (!$group->relationLoaded('rules')) { + Log::debug('Group rules have NOT been pre-loaded, load them NOW.'); + $rules = $group->rules() + ->orderBy('rules.order', 'ASC') +// ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') +// ->where('rule_triggers.trigger_type', 'user_action') +// ->where('rule_triggers.trigger_value', 'store-journal') + ->where('rules.active', true) + ->get(['rules.*']) + ; + } + Log::debug(sprintf('Going to fire group #%d with %d rule(s)', $group->id, $rules->count())); /** @var Rule $rule */ foreach ($rules as $rule) { diff --git a/app/Transformers/AbstractTransformer.php b/app/Transformers/AbstractTransformer.php index 054173dd0f..d741ba0cf7 100644 --- a/app/Transformers/AbstractTransformer.php +++ b/app/Transformers/AbstractTransformer.php @@ -34,11 +34,17 @@ abstract class AbstractTransformer extends TransformerAbstract { protected ParameterBag $parameters; + /** + * @deprecated + */ final public function getParameters(): ParameterBag { return $this->parameters; } + /** + * @deprecated + */ final public function setParameters(ParameterBag $parameters): void { $this->parameters = $parameters; diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index c9e5d3aeee..5538e745e7 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -30,7 +30,6 @@ use FireflyIII\Models\Account; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\Facades\Amount; -use Symfony\Component\HttpFoundation\ParameterBag; /** * Class AccountTransformer @@ -46,7 +45,6 @@ class AccountTransformer extends AbstractTransformer */ public function __construct() { - $this->parameters = new ParameterBag(); $this->repository = app(AccountRepositoryInterface::class); $this->convertToPrimary = Amount::convertToPrimary(); $this->primary = Amount::getPrimaryCurrency(); diff --git a/changelog.md b/changelog.md index e04c8b79a9..19fdba3fa6 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,42 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## 6.4.3 - 2025-11-01 + +### Added + +- [PR 10993](https://github.com/firefly-iii/firefly-iii/pull/10993) (use correct translation key for category report income table) reported by @ctrl-f5 +- [PR 11028](https://github.com/firefly-iii/firefly-iii/pull/11028) (Adding Latin American Currency Support) reported by @codearena-bot +- [PR 11039](https://github.com/firefly-iii/firefly-iii/pull/11039) (proposal for improved request handling) reported by @ctrl-f5 +- [PR 11041](https://github.com/firefly-iii/firefly-iii/pull/11041) (Add XML mimetypes to the allowedMimes list) reported by @jreyesr +- [Issue 11102](https://github.com/firefly-iii/firefly-iii/issues/11102) (Allow to redact account totals in web ui) reported by @edvgui +- [Issue 11132](https://github.com/firefly-iii/firefly-iii/issues/11132) (Add a 6 month interest period) reported by @CiramorDev + +### Fixed + +- [Issue 11109](https://github.com/firefly-iii/firefly-iii/issues/11109) (Could not find a transaction currency with code "Korean Won"/"Russian ruble") reported by @mattephi +- [PR 11019](https://github.com/firefly-iii/firefly-iii/pull/11019) (fix issue 11015 set end date time to end of day) reported by @ctrl-f5 +- [PR 11024](https://github.com/firefly-iii/firefly-iii/pull/11024) (improved balance range date handling) reported by @ctrl-f5 +- [Issue 11031](https://github.com/firefly-iii/firefly-iii/issues/11031) (Rule with trigger "When a transaction is created" being triggered on transaction updates) reported by @pvieira84 +- [Issue 11038](https://github.com/firefly-iii/firefly-iii/issues/11038) (Missing date formats when using "MTD" on the frontpage) reported by @cristiangrossano +- [Issue 11042](https://github.com/firefly-iii/firefly-iii/issues/11042) (Option to apply rules on a specific date range has disappeared from the UI) reported by @angelbarrera92 +- [PR 11052](https://github.com/firefly-iii/firefly-iii/pull/11052) (correct validator function to check for errors + `Account\ShowControllerTest`) reported by @ctrl-f5 +- [Issue 11054](https://github.com/firefly-iii/firefly-iii/issues/11054) (Autocreation of revenue account when depositing to liability) reported by @Mr-Kanister +- [Issue 11063](https://github.com/firefly-iii/firefly-iii/issues/11063) ("bcadd: argument must be of type string, int given" when viewing categories or tags) reported by @df911 +- [Discussion 11104](https://github.com/orgs/firefly-iii/discussions/11104) (Personal Access Token failing on 6.4.2 for data-importer) started by @marcoblancas +- [Discussion 11122](https://github.com/orgs/firefly-iii/discussions/11122) (Convenient Rules Creation) started by @viewmatic2025 +- [Issue 11134](https://github.com/firefly-iii/firefly-iii/issues/11134) (Rules are not applied if description in quotes) reported by @beatbesmer + +### Security + +- Debug route is now behind 2FA + +### API + +- [Issue 11050](https://github.com/firefly-iii/firefly-iii/issues/11050) (Dev Version: `/api/v1/accounts/get/$id` requires (optional) `start` parameter, throws error otherwise) reported by @dreautall +- [PR 11056](https://github.com/firefly-iii/firefly-iii/pull/11056) (account/attachments endpoint use request object for pagination, add test) reported by @ctrl-f5 +- [Issue 11096](https://github.com/firefly-iii/firefly-iii/issues/11096) (Budget Limit API ignores transactions on the last day of the month) reported by @edbingo + ## 6.4.2 - 2055-10-07 diff --git a/composer.lock b/composer.lock index 2cdbc5e87e..35d94930a5 100644 --- a/composer.lock +++ b/composer.lock @@ -1878,16 +1878,16 @@ }, { "name": "laravel/framework", - "version": "v12.32.5", + "version": "v12.36.1", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a" + "reference": "cad110d7685fbab990a6bb8184d0cfd847d7c4d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/77b2740391cd2a825ba59d6fada45e9b8b9bcc5a", - "reference": "77b2740391cd2a825ba59d6fada45e9b8b9bcc5a", + "url": "https://api.github.com/repos/laravel/framework/zipball/cad110d7685fbab990a6bb8184d0cfd847d7c4d8", + "reference": "cad110d7685fbab990a6bb8184d0cfd847d7c4d8", "shasum": "" }, "require": { @@ -1999,7 +1999,7 @@ "league/flysystem-sftp-v3": "^3.25.1", "mockery/mockery": "^1.6.10", "opis/json-schema": "^2.4.1", - "orchestra/testbench-core": "^10.6.5", + "orchestra/testbench-core": "^10.7.0", "pda/pheanstalk": "^5.0.6|^7.0.0", "php-http/discovery": "^1.15", "phpstan/phpstan": "^2.0", @@ -2093,7 +2093,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-09-30T17:39:22+00:00" + "time": "2025-10-29T14:20:57+00:00" }, { "name": "laravel/passport", @@ -2296,16 +2296,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v2.0.5", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed" + "reference": "038ce42edee619599a1debb7e81d7b3759492819" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/3832547db6e0e2f8bb03d4093857b378c66eceed", - "reference": "3832547db6e0e2f8bb03d4093857b378c66eceed", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/038ce42edee619599a1debb7e81d7b3759492819", + "reference": "038ce42edee619599a1debb7e81d7b3759492819", "shasum": "" }, "require": { @@ -2353,7 +2353,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2025-09-22T17:29:40+00:00" + "time": "2025-10-09T13:42:30+00:00" }, { "name": "laravel/slack-notification-channel", @@ -2485,34 +2485,34 @@ }, { "name": "lcobucci/clock", - "version": "3.3.1", + "version": "3.5.0", "source": { "type": "git", "url": "https://github.com/lcobucci/clock.git", - "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b" + "reference": "a3139d9e97d47826f27e6a17bb63f13621f86058" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/clock/zipball/db3713a61addfffd615b79bf0bc22f0ccc61b86b", - "reference": "db3713a61addfffd615b79bf0bc22f0ccc61b86b", + "url": "https://api.github.com/repos/lcobucci/clock/zipball/a3139d9e97d47826f27e6a17bb63f13621f86058", + "reference": "a3139d9e97d47826f27e6a17bb63f13621f86058", "shasum": "" }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.3.0 || ~8.4.0 || ~8.5.0", "psr/clock": "^1.0" }, "provide": { "psr/clock-implementation": "1.0" }, "require-dev": { - "infection/infection": "^0.29", - "lcobucci/coding-standard": "^11.1.0", + "infection/infection": "^0.31", + "lcobucci/coding-standard": "^11.2.0", "phpstan/extension-installer": "^1.3.1", - "phpstan/phpstan": "^1.10.25", - "phpstan/phpstan-deprecation-rules": "^1.1.3", - "phpstan/phpstan-phpunit": "^1.3.13", - "phpstan/phpstan-strict-rules": "^1.5.1", - "phpunit/phpunit": "^11.3.6" + "phpstan/phpstan": "^2.0.0", + "phpstan/phpstan-deprecation-rules": "^2.0.0", + "phpstan/phpstan-phpunit": "^2.0.0", + "phpstan/phpstan-strict-rules": "^2.0.0", + "phpunit/phpunit": "^12.0.0" }, "type": "library", "autoload": { @@ -2533,7 +2533,7 @@ "description": "Yet another clock abstraction", "support": { "issues": "https://github.com/lcobucci/clock/issues", - "source": "https://github.com/lcobucci/clock/tree/3.3.1" + "source": "https://github.com/lcobucci/clock/tree/3.5.0" }, "funding": [ { @@ -2545,26 +2545,26 @@ "type": "patreon" } ], - "time": "2024-09-24T20:45:14+00:00" + "time": "2025-10-27T09:03:17+00:00" }, { "name": "lcobucci/jwt", - "version": "5.5.0", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "a835af59b030d3f2967725697cf88300f579088e" + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a835af59b030d3f2967725697cf88300f579088e", - "reference": "a835af59b030d3f2967725697cf88300f579088e", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/bb3e9f21e4196e8afc41def81ef649c164bca25e", + "reference": "bb3e9f21e4196e8afc41def81ef649c164bca25e", "shasum": "" }, "require": { "ext-openssl": "*", "ext-sodium": "*", - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "psr/clock": "^1.0" }, "require-dev": { @@ -2606,7 +2606,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.5.0" + "source": "https://github.com/lcobucci/jwt/tree/5.6.0" }, "funding": [ { @@ -2618,7 +2618,7 @@ "type": "patreon" } ], - "time": "2025-01-26T21:29:45+00:00" + "time": "2025-10-17T11:30:53+00:00" }, { "name": "league/commonmark", @@ -2811,16 +2811,16 @@ }, { "name": "league/csv", - "version": "9.26.0", + "version": "9.27.1", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571" + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/7fce732754d043f3938899e5183e2d0f3d31b571", - "reference": "7fce732754d043f3938899e5183e2d0f3d31b571", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/26de738b8fccf785397d05ee2fc07b6cd8749797", + "reference": "26de738b8fccf785397d05ee2fc07b6cd8749797", "shasum": "" }, "require": { @@ -2898,7 +2898,7 @@ "type": "github" } ], - "time": "2025-10-01T11:24:54+00:00" + "time": "2025-10-25T08:35:20+00:00" }, { "name": "league/event", @@ -2956,16 +2956,16 @@ }, { "name": "league/flysystem", - "version": "3.30.0", + "version": "3.30.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e" + "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/2203e3151755d874bb2943649dae1eb8533ac93e", - "reference": "2203e3151755d874bb2943649dae1eb8533ac93e", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/c139fd65c1f796b926f4aec0df37f6caa959a8da", + "reference": "c139fd65c1f796b926f4aec0df37f6caa959a8da", "shasum": "" }, "require": { @@ -3033,9 +3033,9 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.30.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.30.1" }, - "time": "2025-06-25T13:29:59+00:00" + "time": "2025-10-20T15:35:26+00:00" }, { "name": "league/flysystem-local", @@ -3476,22 +3476,22 @@ }, { "name": "mailersend/laravel-driver", - "version": "v2.11.0", + "version": "v2.12.0", "source": { "type": "git", "url": "https://github.com/mailersend/mailersend-laravel-driver.git", - "reference": "63acebb5064745076df27b1a80423986b6d7b69e" + "reference": "15e1ec41e29e65d3ca226929c65804190aaa93eb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mailersend/mailersend-laravel-driver/zipball/63acebb5064745076df27b1a80423986b6d7b69e", - "reference": "63acebb5064745076df27b1a80423986b6d7b69e", + "url": "https://api.github.com/repos/mailersend/mailersend-laravel-driver/zipball/15e1ec41e29e65d3ca226929c65804190aaa93eb", + "reference": "15e1ec41e29e65d3ca226929c65804190aaa93eb", "shasum": "" }, "require": { "ext-json": "*", "illuminate/support": "^9.0 || ^10.0 || ^11.0 || ^12.0", - "mailersend/mailersend": "^0.34.0", + "mailersend/mailersend": "^0.35.0", "nyholm/psr7": "^1.5", "php": ">=8.0", "php-http/guzzle7-adapter": "^1.0", @@ -3539,22 +3539,22 @@ ], "support": { "issues": "https://github.com/mailersend/mailersend-laravel-driver/issues", - "source": "https://github.com/mailersend/mailersend-laravel-driver/tree/v2.11.0" + "source": "https://github.com/mailersend/mailersend-laravel-driver/tree/v2.12.0" }, - "time": "2025-06-04T08:47:41+00:00" + "time": "2025-10-28T14:59:16+00:00" }, { "name": "mailersend/mailersend", - "version": "v0.34.0", + "version": "v0.35.0", "source": { "type": "git", "url": "https://github.com/mailersend/mailersend-php.git", - "reference": "1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab" + "reference": "f1696cf9e727e9503fbc5882d2a111bd966ad276" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mailersend/mailersend-php/zipball/1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab", - "reference": "1cb8c42e5569e7455b1e0e794dcbf68e3b7898ab", + "url": "https://api.github.com/repos/mailersend/mailersend-php/zipball/f1696cf9e727e9503fbc5882d2a111bd966ad276", + "reference": "f1696cf9e727e9503fbc5882d2a111bd966ad276", "shasum": "" }, "require": { @@ -3605,9 +3605,9 @@ ], "support": { "issues": "https://github.com/mailersend/mailersend-php/issues", - "source": "https://github.com/mailersend/mailersend-php/tree/v0.34.0" + "source": "https://github.com/mailersend/mailersend-php/tree/v0.35.0" }, - "time": "2025-06-04T07:53:52+00:00" + "time": "2025-10-28T13:11:43+00:00" }, { "name": "monolog/monolog", @@ -3819,25 +3819,25 @@ }, { "name": "nette/schema", - "version": "v1.3.2", + "version": "v1.3.3", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d" + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/da801d52f0354f70a638673c4a0f04e16529431d", - "reference": "da801d52f0354f70a638673c4a0f04e16529431d", + "url": "https://api.github.com/repos/nette/schema/zipball/2befc2f42d7c715fd9d95efc31b1081e5d765004", + "reference": "2befc2f42d7c715fd9d95efc31b1081e5d765004", "shasum": "" }, "require": { "nette/utils": "^4.0", - "php": "8.1 - 8.4" + "php": "8.1 - 8.5" }, "require-dev": { "nette/tester": "^2.5.2", - "phpstan/phpstan-nette": "^1.0", + "phpstan/phpstan-nette": "^2.0@stable", "tracy/tracy": "^2.8" }, "type": "library", @@ -3847,6 +3847,9 @@ } }, "autoload": { + "psr-4": { + "Nette\\": "src" + }, "classmap": [ "src/" ] @@ -3875,9 +3878,9 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.3.2" + "source": "https://github.com/nette/schema/tree/v1.3.3" }, - "time": "2024-10-06T23:10:23+00:00" + "time": "2025-10-30T22:57:59+00:00" }, { "name": "nette/utils", @@ -4069,31 +4072,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.3.1", + "version": "v2.3.2", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123" + "reference": "eb61920a53057a7debd718a5b89c2178032b52c0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/dfa08f390e509967a15c22493dc0bac5733d9123", - "reference": "dfa08f390e509967a15c22493dc0bac5733d9123", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/eb61920a53057a7debd718a5b89c2178032b52c0", + "reference": "eb61920a53057a7debd718a5b89c2178032b52c0", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.2.6" + "symfony/console": "^7.3.4" }, "require-dev": { - "illuminate/console": "^11.44.7", - "laravel/pint": "^1.22.0", + "illuminate/console": "^11.46.1", + "laravel/pint": "^1.25.1", "mockery/mockery": "^1.6.12", - "pestphp/pest": "^2.36.0 || ^3.8.2", - "phpstan/phpstan": "^1.12.25", + "pestphp/pest": "^2.36.0 || ^3.8.4", + "phpstan/phpstan": "^1.12.32", "phpstan/phpstan-strict-rules": "^1.6.2", - "symfony/var-dumper": "^7.2.6", + "symfony/var-dumper": "^7.3.4", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -4136,7 +4139,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.3.1" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.2" }, "funding": [ { @@ -4152,7 +4155,7 @@ "type": "github" } ], - "time": "2025-05-08T08:14:37+00:00" + "time": "2025-10-18T11:10:27+00:00" }, { "name": "nyholm/psr7", @@ -6416,16 +6419,16 @@ }, { "name": "symfony/cache", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f" + "reference": "4a55feb59664f49042a0824c0f955e2f4c1412ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", - "reference": "bf8afc8ffd4bfd3d9c373e417f041d9f1e5b863f", + "url": "https://api.github.com/repos/symfony/cache/zipball/4a55feb59664f49042a0824c0f955e2f4c1412ad", + "reference": "4a55feb59664f49042a0824c0f955e2f4c1412ad", "shasum": "" }, "require": { @@ -6494,7 +6497,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.3.4" + "source": "https://github.com/symfony/cache/tree/v7.3.5" }, "funding": [ { @@ -6514,7 +6517,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-10-16T13:55:38+00:00" }, { "name": "symfony/cache-contracts", @@ -6668,16 +6671,16 @@ }, { "name": "symfony/console", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db" + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", - "reference": "2b9c5fafbac0399a20a2e82429e2bd735dcfb7db", + "url": "https://api.github.com/repos/symfony/console/zipball/cdb80fa5869653c83cfe1a9084a673b6daf57ea7", + "reference": "cdb80fa5869653c83cfe1a9084a673b6daf57ea7", "shasum": "" }, "require": { @@ -6742,7 +6745,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.3.4" + "source": "https://github.com/symfony/console/tree/v7.3.5" }, "funding": [ { @@ -6762,7 +6765,7 @@ "type": "tidelift" } ], - "time": "2025-09-22T15:31:00+00:00" + "time": "2025-10-14T15:46:26+00:00" }, { "name": "symfony/css-selector", @@ -7207,16 +7210,16 @@ }, { "name": "symfony/finder", - "version": "v7.3.2", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe" + "reference": "9f696d2f1e340484b4683f7853b273abff94421f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2a6614966ba1074fa93dae0bc804227422df4dfe", - "reference": "2a6614966ba1074fa93dae0bc804227422df4dfe", + "url": "https://api.github.com/repos/symfony/finder/zipball/9f696d2f1e340484b4683f7853b273abff94421f", + "reference": "9f696d2f1e340484b4683f7853b273abff94421f", "shasum": "" }, "require": { @@ -7251,7 +7254,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.3.2" + "source": "https://github.com/symfony/finder/tree/v7.3.5" }, "funding": [ { @@ -7271,7 +7274,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T13:41:35+00:00" + "time": "2025-10-15T18:45:57+00:00" }, { "name": "symfony/http-client", @@ -7453,16 +7456,16 @@ }, { "name": "symfony/http-foundation", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6" + "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/c061c7c18918b1b64268771aad04b40be41dd2e6", - "reference": "c061c7c18918b1b64268771aad04b40be41dd2e6", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ce31218c7cac92eab280762c4375fb70a6f4f897", + "reference": "ce31218c7cac92eab280762c4375fb70a6f4f897", "shasum": "" }, "require": { @@ -7512,7 +7515,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.3.4" + "source": "https://github.com/symfony/http-foundation/tree/v7.3.5" }, "funding": [ { @@ -7532,20 +7535,20 @@ "type": "tidelift" } ], - "time": "2025-09-16T08:38:17+00:00" + "time": "2025-10-24T21:42:11+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "b796dffea7821f035047235e076b60ca2446e3cf" + "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/b796dffea7821f035047235e076b60ca2446e3cf", - "reference": "b796dffea7821f035047235e076b60ca2446e3cf", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/24fd3f123532e26025f49f1abefcc01a69ef15ab", + "reference": "24fd3f123532e26025f49f1abefcc01a69ef15ab", "shasum": "" }, "require": { @@ -7630,7 +7633,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.3.4" + "source": "https://github.com/symfony/http-kernel/tree/v7.3.5" }, "funding": [ { @@ -7650,20 +7653,20 @@ "type": "tidelift" } ], - "time": "2025-09-27T12:32:17+00:00" + "time": "2025-10-28T10:19:01+00:00" }, { "name": "symfony/mailer", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d" + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/ab97ef2f7acf0216955f5845484235113047a31d", - "reference": "ab97ef2f7acf0216955f5845484235113047a31d", + "url": "https://api.github.com/repos/symfony/mailer/zipball/fd497c45ba9c10c37864e19466b090dcb60a50ba", + "reference": "fd497c45ba9c10c37864e19466b090dcb60a50ba", "shasum": "" }, "require": { @@ -7714,7 +7717,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.3.4" + "source": "https://github.com/symfony/mailer/tree/v7.3.5" }, "funding": [ { @@ -7734,7 +7737,7 @@ "type": "tidelift" } ], - "time": "2025-09-17T05:51:54+00:00" + "time": "2025-10-24T14:27:20+00:00" }, { "name": "symfony/mailgun-mailer", @@ -9453,16 +9456,16 @@ }, { "name": "symfony/var-dumper", - "version": "v7.3.4", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb" + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", - "reference": "b8abe7daf2730d07dfd4b2ee1cecbf0dd2fbdabb", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/476c4ae17f43a9a36650c69879dcf5b1e6ae724d", + "reference": "476c4ae17f43a9a36650c69879dcf5b1e6ae724d", "shasum": "" }, "require": { @@ -9516,7 +9519,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.3.4" + "source": "https://github.com/symfony/var-dumper/tree/v7.3.5" }, "funding": [ { @@ -9536,7 +9539,7 @@ "type": "tidelift" } ], - "time": "2025-09-11T10:12:26+00:00" + "time": "2025-09-27T09:00:46+00:00" }, { "name": "symfony/var-exporter", @@ -9815,16 +9818,16 @@ }, { "name": "twig/twig", - "version": "v3.21.1", + "version": "v3.22.0", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d" + "reference": "4509984193026de413baf4ba80f68590a7f2c51d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/285123877d4dd97dd7c11842ac5fb7e86e60d81d", - "reference": "285123877d4dd97dd7c11842ac5fb7e86e60d81d", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/4509984193026de413baf4ba80f68590a7f2c51d", + "reference": "4509984193026de413baf4ba80f68590a7f2c51d", "shasum": "" }, "require": { @@ -9878,7 +9881,7 @@ ], "support": { "issues": "https://github.com/twigphp/Twig/issues", - "source": "https://github.com/twigphp/Twig/tree/v3.21.1" + "source": "https://github.com/twigphp/Twig/tree/v3.22.0" }, "funding": [ { @@ -9890,7 +9893,7 @@ "type": "tidelift" } ], - "time": "2025-05-03T07:21:55+00:00" + "time": "2025-10-29T15:56:47+00:00" }, { "name": "vlucas/phpdotenv", @@ -10052,28 +10055,28 @@ }, { "name": "webmozart/assert", - "version": "1.11.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/webmozarts/assert.git", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991" + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozarts/assert/zipball/11cb2199493b2f8a3b53e7f19068fc6aac760991", - "reference": "11cb2199493b2f8a3b53e7f19068fc6aac760991", + "url": "https://api.github.com/repos/webmozarts/assert/zipball/9be6926d8b485f55b9229203f962b51ed377ba68", + "reference": "9be6926d8b485f55b9229203f962b51ed377ba68", "shasum": "" }, "require": { "ext-ctype": "*", + "ext-date": "*", + "ext-filter": "*", "php": "^7.2 || ^8.0" }, - "conflict": { - "phpstan/phpstan": "<0.12.20", - "vimeo/psalm": "<4.6.1 || 4.6.2" - }, - "require-dev": { - "phpunit/phpunit": "^8.5.13" + "suggest": { + "ext-intl": "", + "ext-simplexml": "", + "ext-spl": "" }, "type": "library", "extra": { @@ -10104,9 +10107,9 @@ ], "support": { "issues": "https://github.com/webmozarts/assert/issues", - "source": "https://github.com/webmozarts/assert/tree/1.11.0" + "source": "https://github.com/webmozarts/assert/tree/1.12.1" }, - "time": "2022-06-03T18:03:27+00:00" + "time": "2025-10-29T15:56:20+00:00" } ], "packages-dev": [ @@ -10549,16 +10552,16 @@ }, { "name": "driftingly/rector-laravel", - "version": "2.0.7", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/driftingly/rector-laravel.git", - "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e" + "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/625dc02cee08d47ecf0ac86de2f02a55026cf34e", - "reference": "625dc02cee08d47ecf0ac86de2f02a55026cf34e", + "url": "https://api.github.com/repos/driftingly/rector-laravel/zipball/abc336cbf06f53d90ab74cecfd319379fc55d408", + "reference": "abc336cbf06f53d90ab74cecfd319379fc55d408", "shasum": "" }, "require": { @@ -10578,9 +10581,9 @@ "description": "Rector upgrades rules for Laravel Framework", "support": { "issues": "https://github.com/driftingly/rector-laravel/issues", - "source": "https://github.com/driftingly/rector-laravel/tree/2.0.7" + "source": "https://github.com/driftingly/rector-laravel/tree/2.1.1" }, - "time": "2025-08-19T20:49:47+00:00" + "time": "2025-10-23T13:53:44+00:00" }, { "name": "fakerphp/faker", @@ -11036,16 +11039,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.1", + "version": "v5.6.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" + "reference": "3a454ca033b9e06b63282ce19562e892747449bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", - "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", + "reference": "3a454ca033b9e06b63282ce19562e892747449bb", "shasum": "" }, "require": { @@ -11088,9 +11091,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" }, - "time": "2025-08-13T20:13:15+00:00" + "time": "2025-10-21T19:32:17+00:00" }, { "name": "phar-io/manifest", @@ -11333,11 +11336,11 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.30", + "version": "2.1.31", "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/a4a7f159927983dd4f7c8020ed227d80b7f39d7d", - "reference": "a4a7f159927983dd4f7c8020ed227d80b7f39d7d", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/ead89849d879fe203ce9292c6ef5e7e76f867b96", + "reference": "ead89849d879fe203ce9292c6ef5e7e76f867b96", "shasum": "" }, "require": { @@ -11382,7 +11385,7 @@ "type": "github" } ], - "time": "2025-10-02T16:07:52+00:00" + "time": "2025-10-10T14:14:11+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", @@ -11815,16 +11818,16 @@ }, { "name": "phpunit/phpunit", - "version": "12.4.0", + "version": "12.4.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9" + "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f62aab5794e36ccd26860db2d1bbf89ac19028d9", - "reference": "f62aab5794e36ccd26860db2d1bbf89ac19028d9", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a94ea4d26d865875803b23aaf78c3c2c670ea2ea", + "reference": "a94ea4d26d865875803b23aaf78c3c2c670ea2ea", "shasum": "" }, "require": { @@ -11892,7 +11895,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.0" + "source": "https://github.com/sebastianbergmann/phpunit/tree/12.4.2" }, "funding": [ { @@ -11916,20 +11919,20 @@ "type": "tidelift" } ], - "time": "2025-10-03T04:28:03+00:00" + "time": "2025-10-30T08:41:39+00:00" }, { "name": "rector/rector", - "version": "2.2.1", + "version": "2.2.7", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb" + "reference": "022038537838bc8a4e526af86c2d6e38eaeff7ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/e1aaf3061e9ae9342ed0824865e3a3360defddeb", - "reference": "e1aaf3061e9ae9342ed0824865e3a3360defddeb", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/022038537838bc8a4e526af86c2d6e38eaeff7ef", + "reference": "022038537838bc8a4e526af86c2d6e38eaeff7ef", "shasum": "" }, "require": { @@ -11968,7 +11971,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.2.1" + "source": "https://github.com/rectorphp/rector/tree/2.2.7" }, "funding": [ { @@ -11976,7 +11979,7 @@ "type": "github" } ], - "time": "2025-10-06T21:25:14+00:00" + "time": "2025-10-29T15:46:12+00:00" }, { "name": "sebastian/cli-parser", diff --git a/config/firefly.php b/config/firefly.php index 8bdd78ce6e..ff61d3b3f8 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -78,8 +78,8 @@ return [ 'running_balance_column' => env('USE_RUNNING_BALANCE', false), // see cer.php for exchange rates feature flag. ], - 'version' => '6.4.2', - 'build_time' => 1759824590, + 'version' => '6.4.3', + 'build_time' => 1761889776, 'api_version' => '2.1.0', // field is no longer used. 'db_version' => 28, // field is no longer used. @@ -182,6 +182,7 @@ return [ 'darkMode' => 'browser', 'list_length' => 10, // to be removed if v1 is cancelled. 'default_preferences' => [ + 'anonymous' => false, 'frontpageAccounts' => [], 'listPageSize' => 50, 'currencyPreference' => 'EUR', @@ -225,6 +226,8 @@ return [ // plain files 'text/plain', 'text/html', + 'text/xml', + 'application/xml', // images 'image/jpeg', @@ -306,7 +309,7 @@ return [ // "period must be in this list" values 'bill_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], - 'interest_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], + 'interest_periods' => ['daily', 'weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], // settings to translate X to Y 'range_to_repeat_freq' => [ diff --git a/database/factories/AccountFactory.php b/database/factories/AccountFactory.php new file mode 100644 index 0000000000..dd2eb4ac04 --- /dev/null +++ b/database/factories/AccountFactory.php @@ -0,0 +1,24 @@ + $this->faker->name(), + 'active' => true, + ]; + } + + public function withType(AccountTypeEnum $type): static + { + return $this->for(AccountType::where('type', $type->value)->first()); + } +} diff --git a/database/migrations/2025_09_25_175248_create_period_statistics.php b/database/migrations/2025_09_25_175248_create_period_statistics.php index 0cda62b0ef..1e118fc58d 100644 --- a/database/migrations/2025_09_25_175248_create_period_statistics.php +++ b/database/migrations/2025_09_25_175248_create_period_statistics.php @@ -11,34 +11,36 @@ return new class extends Migration */ public function up(): void { - Schema::create('period_statistics', function (Blueprint $table) { - $table->id(); - $table->timestamps(); + if (!Schema::hasTable('period_statistics')) { + Schema::create('period_statistics', function (Blueprint $table) { + $table->id(); + $table->timestamps(); - // reference to user group id. - $table->bigInteger('user_group_id', false, true); + // reference to user group id. + $table->bigInteger('user_group_id', false, true); - $table->integer('primary_statable_id', false, true)->nullable(); - $table->string('primary_statable_type', 255)->nullable(); + $table->integer('primary_statable_id', false, true)->nullable(); + $table->string('primary_statable_type', 255)->nullable(); - $table->integer('secondary_statable_id', false, true)->nullable(); - $table->string('secondary_statable_type', 255)->nullable(); + $table->integer('secondary_statable_id', false, true)->nullable(); + $table->string('secondary_statable_type', 255)->nullable(); - $table->integer('tertiary_statable_id', false, true)->nullable(); - $table->string('tertiary_statable_type', 255)->nullable(); + $table->integer('tertiary_statable_id', false, true)->nullable(); + $table->string('tertiary_statable_type', 255)->nullable(); - $table->integer('transaction_currency_id', false, true); - $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); + $table->integer('transaction_currency_id', false, true); + $table->foreign('transaction_currency_id')->references('id')->on('transaction_currencies')->onDelete('cascade'); - $table->dateTime('start')->nullable(); - $table->string('start_tz', 50)->nullable(); - $table->dateTime('end')->nullable(); - $table->string('end_tz', 50)->nullable(); - $table->string('type',255); - $table->integer('count', false, true)->default(0); - $table->decimal('amount', 32, 12); - $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); - }); + $table->dateTime('start')->nullable(); + $table->string('start_tz', 50)->nullable(); + $table->dateTime('end')->nullable(); + $table->string('end_tz', 50)->nullable(); + $table->string('type', 255); + $table->integer('count', false, true)->default(0); + $table->decimal('amount', 32, 12); + $table->foreign('user_group_id')->references('id')->on('user_groups')->onDelete('cascade'); + }); + } } /** diff --git a/database/seeders/TransactionCurrencySeeder.php b/database/seeders/TransactionCurrencySeeder.php index ce75ec253d..5673362389 100644 --- a/database/seeders/TransactionCurrencySeeder.php +++ b/database/seeders/TransactionCurrencySeeder.php @@ -53,6 +53,10 @@ class TransactionCurrencySeeder extends Seeder $currencies[] = ['code' => 'BRL', 'name' => 'Brazilian real', 'symbol' => 'R$', 'decimal_places' => 2]; $currencies[] = ['code' => 'CAD', 'name' => 'Canadian dollar', 'symbol' => 'C$', 'decimal_places' => 2]; $currencies[] = ['code' => 'MXN', 'name' => 'Mexican peso', 'symbol' => 'MX$', 'decimal_places' => 2]; + $currencies[] = ['code' => 'PEN', 'name' => 'Peruvian Sol', 'symbol' => 'S/', 'decimal_places' => 2]; + $currencies[] = ['code' => 'ARS', 'name' => 'Argentinian Peso', 'symbol' => '$', 'decimal_places' => 2]; + $currencies[] = ['code' => 'COP', 'name' => 'Colombian Peso', 'symbol' => '$', 'decimal_places' => 2]; + $currencies[] = ['code' => 'CLP', 'name' => 'Chilean Peso', 'symbol' => '$', 'decimal_places' => 2]; // oceanian currencies $currencies[] = ['code' => 'IDR', 'name' => 'Indonesian rupiah', 'symbol' => 'Rp', 'decimal_places' => 2]; diff --git a/package-lock.json b/package-lock.json index a9ba1b32a1..6f0fc7e4e1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -29,9 +29,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.4.tgz", - "integrity": "sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.5.tgz", + "integrity": "sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==", "dev": true, "license": "MIT", "engines": { @@ -39,21 +39,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.4.tgz", - "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", + "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-module-transforms": "^7.28.3", "@babel/helpers": "^7.28.4", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/traverse": "^7.28.4", - "@babel/types": "^7.28.4", + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -80,14 +80,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.3.tgz", - "integrity": "sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz", + "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.3", - "@babel/types": "^7.28.2", + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -137,18 +137,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.3.tgz", - "integrity": "sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.5.tgz", + "integrity": "sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", "@babel/helper-replace-supers": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.28.3", + "@babel/traverse": "^7.28.5", "semver": "^6.3.1" }, "engines": { @@ -169,14 +169,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -224,14 +224,14 @@ } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -353,9 +353,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -402,13 +402,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.4.tgz", - "integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.4" + "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" @@ -418,14 +418,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -679,9 +679,9 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.4.tgz", - "integrity": "sha512-1yxmvN0MJHOhPVmAsmoW5liWwoILobu/d/ShymZmj867bAdxGbehIrew1DuLpw2Ukv+qDSSPQdYW1dLNE7t11A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", "dev": true, "license": "MIT", "dependencies": { @@ -767,14 +767,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.0.tgz", - "integrity": "sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.28.0" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -867,9 +867,9 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", "dev": true, "license": "MIT", "dependencies": { @@ -966,9 +966,9 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", "dev": true, "license": "MIT", "dependencies": { @@ -1032,16 +1032,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.3", "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1186,9 +1186,9 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1319,9 +1319,9 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.3.tgz", - "integrity": "sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.28.5.tgz", + "integrity": "sha512-20NUVgOrinudkIBzQ2bNxP08YpKprUkRTiRSd2/Z5GOdPImJGkoN4Z7IQe1T5AdyKI1i5L6RBmluqdSzvaq9/w==", "dev": true, "license": "MIT", "dependencies": { @@ -1498,17 +1498,17 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.3", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.3.tgz", - "integrity": "sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.0", + "@babel/compat-data": "^7.28.5", "@babel/helper-compilation-targets": "^7.27.2", "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", @@ -1521,42 +1521,42 @@ "@babel/plugin-transform-async-generator-functions": "^7.28.0", "@babel/plugin-transform-async-to-generator": "^7.27.1", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.28.0", + "@babel/plugin-transform-block-scoping": "^7.28.5", "@babel/plugin-transform-class-properties": "^7.27.1", "@babel/plugin-transform-class-static-block": "^7.28.3", - "@babel/plugin-transform-classes": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.27.1", "@babel/plugin-transform-duplicate-keys": "^7.27.1", "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.0", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", "@babel/plugin-transform-json-strings": "^7.27.1", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", "@babel/plugin-transform-modules-umd": "^7.27.1", "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.28.0", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", "@babel/plugin-transform-object-super": "^7.27.1", "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", "@babel/plugin-transform-parameters": "^7.27.7", "@babel/plugin-transform-private-methods": "^7.27.1", "@babel/plugin-transform-private-property-in-object": "^7.27.1", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.3", + "@babel/plugin-transform-regenerator": "^7.28.4", "@babel/plugin-transform-regexp-modifiers": "^7.27.1", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1632,18 +1632,18 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.4.tgz", - "integrity": "sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz", + "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.28.3", + "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.4", + "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", - "@babel/types": "^7.28.4", + "@babel/types": "^7.28.5", "debug": "^4.3.1" }, "engines": { @@ -1651,14 +1651,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.4.tgz", - "integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1693,9 +1693,9 @@ "license": "MIT" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.10.tgz", - "integrity": "sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", + "integrity": "sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==", "cpu": [ "ppc64" ], @@ -1710,9 +1710,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.10.tgz", - "integrity": "sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.11.tgz", + "integrity": "sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==", "cpu": [ "arm" ], @@ -1727,9 +1727,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.10.tgz", - "integrity": "sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.11.tgz", + "integrity": "sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==", "cpu": [ "arm64" ], @@ -1744,9 +1744,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.10.tgz", - "integrity": "sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.11.tgz", + "integrity": "sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==", "cpu": [ "x64" ], @@ -1761,9 +1761,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.10.tgz", - "integrity": "sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.11.tgz", + "integrity": "sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==", "cpu": [ "arm64" ], @@ -1778,9 +1778,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.10.tgz", - "integrity": "sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.11.tgz", + "integrity": "sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==", "cpu": [ "x64" ], @@ -1795,9 +1795,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.10.tgz", - "integrity": "sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.11.tgz", + "integrity": "sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==", "cpu": [ "arm64" ], @@ -1812,9 +1812,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.10.tgz", - "integrity": "sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.11.tgz", + "integrity": "sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==", "cpu": [ "x64" ], @@ -1829,9 +1829,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.10.tgz", - "integrity": "sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.11.tgz", + "integrity": "sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==", "cpu": [ "arm" ], @@ -1846,9 +1846,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.10.tgz", - "integrity": "sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.11.tgz", + "integrity": "sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==", "cpu": [ "arm64" ], @@ -1863,9 +1863,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.10.tgz", - "integrity": "sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.11.tgz", + "integrity": "sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==", "cpu": [ "ia32" ], @@ -1880,9 +1880,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.10.tgz", - "integrity": "sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.11.tgz", + "integrity": "sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==", "cpu": [ "loong64" ], @@ -1897,9 +1897,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.10.tgz", - "integrity": "sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.11.tgz", + "integrity": "sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==", "cpu": [ "mips64el" ], @@ -1914,9 +1914,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.10.tgz", - "integrity": "sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.11.tgz", + "integrity": "sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==", "cpu": [ "ppc64" ], @@ -1931,9 +1931,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.10.tgz", - "integrity": "sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.11.tgz", + "integrity": "sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==", "cpu": [ "riscv64" ], @@ -1948,9 +1948,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.10.tgz", - "integrity": "sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.11.tgz", + "integrity": "sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==", "cpu": [ "s390x" ], @@ -1965,9 +1965,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.10.tgz", - "integrity": "sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.11.tgz", + "integrity": "sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==", "cpu": [ "x64" ], @@ -1982,9 +1982,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.10.tgz", - "integrity": "sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.11.tgz", + "integrity": "sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==", "cpu": [ "arm64" ], @@ -1999,9 +1999,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.10.tgz", - "integrity": "sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.11.tgz", + "integrity": "sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==", "cpu": [ "x64" ], @@ -2016,9 +2016,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.10.tgz", - "integrity": "sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.11.tgz", + "integrity": "sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==", "cpu": [ "arm64" ], @@ -2033,9 +2033,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.10.tgz", - "integrity": "sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.11.tgz", + "integrity": "sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==", "cpu": [ "x64" ], @@ -2050,9 +2050,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.10.tgz", - "integrity": "sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.11.tgz", + "integrity": "sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==", "cpu": [ "arm64" ], @@ -2067,9 +2067,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.10.tgz", - "integrity": "sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.11.tgz", + "integrity": "sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==", "cpu": [ "x64" ], @@ -2084,9 +2084,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.10.tgz", - "integrity": "sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.11.tgz", + "integrity": "sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==", "cpu": [ "arm64" ], @@ -2101,9 +2101,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.10.tgz", - "integrity": "sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.11.tgz", + "integrity": "sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==", "cpu": [ "ia32" ], @@ -2118,9 +2118,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.10.tgz", - "integrity": "sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.11.tgz", + "integrity": "sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==", "cpu": [ "x64" ], @@ -2589,9 +2589,9 @@ } }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.4.tgz", - "integrity": "sha512-BTm2qKNnWIQ5auf4deoetINJm2JzvihvGb9R6K/ETwKLql/Bb3Eg2H1FBp1gUb4YGbydMA3jcmQTR73q7J+GAA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.52.5.tgz", + "integrity": "sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==", "cpu": [ "arm" ], @@ -2603,9 +2603,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.4.tgz", - "integrity": "sha512-P9LDQiC5vpgGFgz7GSM6dKPCiqR3XYN1WwJKA4/BUVDjHpYsf3iBEmVz62uyq20NGYbiGPR5cNHI7T1HqxNs2w==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.52.5.tgz", + "integrity": "sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==", "cpu": [ "arm64" ], @@ -2617,9 +2617,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.4.tgz", - "integrity": "sha512-QRWSW+bVccAvZF6cbNZBJwAehmvG9NwfWHwMy4GbWi/BQIA/laTIktebT2ipVjNncqE6GLPxOok5hsECgAxGZg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.52.5.tgz", + "integrity": "sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==", "cpu": [ "arm64" ], @@ -2631,9 +2631,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.4.tgz", - "integrity": "sha512-hZgP05pResAkRJxL1b+7yxCnXPGsXU0fG9Yfd6dUaoGk+FhdPKCJ5L1Sumyxn8kvw8Qi5PvQ8ulenUbRjzeCTw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.52.5.tgz", + "integrity": "sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==", "cpu": [ "x64" ], @@ -2645,9 +2645,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.4.tgz", - "integrity": "sha512-xmc30VshuBNUd58Xk4TKAEcRZHaXlV+tCxIXELiE9sQuK3kG8ZFgSPi57UBJt8/ogfhAF5Oz4ZSUBN77weM+mQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.52.5.tgz", + "integrity": "sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==", "cpu": [ "arm64" ], @@ -2659,9 +2659,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.4.tgz", - "integrity": "sha512-WdSLpZFjOEqNZGmHflxyifolwAiZmDQzuOzIq9L27ButpCVpD7KzTRtEG1I0wMPFyiyUdOO+4t8GvrnBLQSwpw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.52.5.tgz", + "integrity": "sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==", "cpu": [ "x64" ], @@ -2673,9 +2673,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.4.tgz", - "integrity": "sha512-xRiOu9Of1FZ4SxVbB0iEDXc4ddIcjCv2aj03dmW8UrZIW7aIQ9jVJdLBIhxBI+MaTnGAKyvMwPwQnoOEvP7FgQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.52.5.tgz", + "integrity": "sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==", "cpu": [ "arm" ], @@ -2687,9 +2687,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.4.tgz", - "integrity": "sha512-FbhM2p9TJAmEIEhIgzR4soUcsW49e9veAQCziwbR+XWB2zqJ12b4i/+hel9yLiD8pLncDH4fKIPIbt5238341Q==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.52.5.tgz", + "integrity": "sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==", "cpu": [ "arm" ], @@ -2701,9 +2701,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.4.tgz", - "integrity": "sha512-4n4gVwhPHR9q/g8lKCyz0yuaD0MvDf7dV4f9tHt0C73Mp8h38UCtSCSE6R9iBlTbXlmA8CjpsZoujhszefqueg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.52.5.tgz", + "integrity": "sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==", "cpu": [ "arm64" ], @@ -2715,9 +2715,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.4.tgz", - "integrity": "sha512-u0n17nGA0nvi/11gcZKsjkLj1QIpAuPFQbR48Subo7SmZJnGxDpspyw2kbpuoQnyK+9pwf3pAoEXerJs/8Mi9g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.52.5.tgz", + "integrity": "sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==", "cpu": [ "arm64" ], @@ -2729,9 +2729,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.4.tgz", - "integrity": "sha512-0G2c2lpYtbTuXo8KEJkDkClE/+/2AFPdPAbmaHoE870foRFs4pBrDehilMcrSScrN/fB/1HTaWO4bqw+ewBzMQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.52.5.tgz", + "integrity": "sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==", "cpu": [ "loong64" ], @@ -2743,9 +2743,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.4.tgz", - "integrity": "sha512-teSACug1GyZHmPDv14VNbvZFX779UqWTsd7KtTM9JIZRDI5NUwYSIS30kzI8m06gOPB//jtpqlhmraQ68b5X2g==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.52.5.tgz", + "integrity": "sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==", "cpu": [ "ppc64" ], @@ -2757,9 +2757,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.4.tgz", - "integrity": "sha512-/MOEW3aHjjs1p4Pw1Xk4+3egRevx8Ji9N6HUIA1Ifh8Q+cg9dremvFCUbOX2Zebz80BwJIgCBUemjqhU5XI5Eg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.52.5.tgz", + "integrity": "sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==", "cpu": [ "riscv64" ], @@ -2771,9 +2771,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.4.tgz", - "integrity": "sha512-1HHmsRyh845QDpEWzOFtMCph5Ts+9+yllCrREuBR/vg2RogAQGGBRC8lDPrPOMnrdOJ+mt1WLMOC2Kao/UwcvA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.52.5.tgz", + "integrity": "sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==", "cpu": [ "riscv64" ], @@ -2785,9 +2785,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.4.tgz", - "integrity": "sha512-seoeZp4L/6D1MUyjWkOMRU6/iLmCU2EjbMTyAG4oIOs1/I82Y5lTeaxW0KBfkUdHAWN7j25bpkt0rjnOgAcQcA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.52.5.tgz", + "integrity": "sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==", "cpu": [ "s390x" ], @@ -2799,9 +2799,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.4.tgz", - "integrity": "sha512-Wi6AXf0k0L7E2gteNsNHUs7UMwCIhsCTs6+tqQ5GPwVRWMaflqGec4Sd8n6+FNFDw9vGcReqk2KzBDhCa1DLYg==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.52.5.tgz", + "integrity": "sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==", "cpu": [ "x64" ], @@ -2813,9 +2813,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.4.tgz", - "integrity": "sha512-dtBZYjDmCQ9hW+WgEkaffvRRCKm767wWhxsFW3Lw86VXz/uJRuD438/XvbZT//B96Vs8oTA8Q4A0AfHbrxP9zw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.52.5.tgz", + "integrity": "sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==", "cpu": [ "x64" ], @@ -2827,9 +2827,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.4.tgz", - "integrity": "sha512-1ox+GqgRWqaB1RnyZXL8PD6E5f7YyRUJYnCqKpNzxzP0TkaUh112NDrR9Tt+C8rJ4x5G9Mk8PQR3o7Ku2RKqKA==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.52.5.tgz", + "integrity": "sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==", "cpu": [ "arm64" ], @@ -2841,9 +2841,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.4.tgz", - "integrity": "sha512-8GKr640PdFNXwzIE0IrkMWUNUomILLkfeHjXBi/nUvFlpZP+FA8BKGKpacjW6OUUHaNI6sUURxR2U2g78FOHWQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.52.5.tgz", + "integrity": "sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==", "cpu": [ "arm64" ], @@ -2855,9 +2855,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.4.tgz", - "integrity": "sha512-AIy/jdJ7WtJ/F6EcfOb2GjR9UweO0n43jNObQMb6oGxkYTfLcnN7vYYpG+CN3lLxrQkzWnMOoNSHTW54pgbVxw==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.52.5.tgz", + "integrity": "sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==", "cpu": [ "ia32" ], @@ -2869,9 +2869,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.4.tgz", - "integrity": "sha512-UF9KfsH9yEam0UjTwAgdK0anlQ7c8/pWPU2yVjyWcF1I1thABt6WXE47cI71pGiZ8wGvxohBoLnxM04L/wj8mQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.52.5.tgz", + "integrity": "sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==", "cpu": [ "x64" ], @@ -2883,9 +2883,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.4.tgz", - "integrity": "sha512-bf9PtUa0u8IXDVxzRToFQKsNCRz9qLYfR/MpECxl4mRoWYjAeFjgxj1XdZr2M/GNVpT05p+LgQOHopYDlUu6/w==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.52.5.tgz", + "integrity": "sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==", "cpu": [ "x64" ], @@ -3034,16 +3034,16 @@ "license": "MIT" }, "node_modules/@types/express": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.23.tgz", - "integrity": "sha512-Crp6WY9aTYP3qPi2wGDo9iUe/rceX01UMhnF1jmwDcKCFM6cx7YhGP/Mpr3y9AASpfHixIG0E6azCcL5OcDHsQ==", + "version": "4.17.25", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", + "integrity": "sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==", "dev": true, "license": "MIT", "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.33", "@types/qs": "*", - "@types/serve-static": "*" + "@types/serve-static": "^1" } }, "node_modules/@types/express-serve-static-core": { @@ -3091,9 +3091,9 @@ "license": "MIT" }, "node_modules/@types/http-proxy": { - "version": "1.17.16", - "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz", - "integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==", + "version": "1.17.17", + "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.17.tgz", + "integrity": "sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==", "dev": true, "license": "MIT", "dependencies": { @@ -3173,13 +3173,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.7.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.7.0.tgz", - "integrity": "sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==", + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~7.14.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/node-forge": { @@ -3221,9 +3221,9 @@ "license": "MIT" }, "node_modules/@types/send": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.0.tgz", - "integrity": "sha512-zBF6vZJn1IaMpg3xUF25VK3gd3l8zwE0ZLRX7dsQyQi+jp4E8mMDJNGDYnYse+bQhYwWERTxVwHpi3dMOq7RKQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3241,9 +3241,9 @@ } }, "node_modules/@types/serve-static": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.9.tgz", - "integrity": "sha512-dOTIuqpWLyl3BBXU3maNQsS4A3zuuoYRNIvYSxxhebPfXg2mzWQEPne/nlJ37yOse6uGgR386uTpdsx4D0QZWA==", + "version": "1.15.10", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.10.tgz", + "integrity": "sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==", "dev": true, "license": "MIT", "dependencies": { @@ -3253,9 +3253,9 @@ } }, "node_modules/@types/serve-static/node_modules/@types/send": { - "version": "0.17.5", - "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", - "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", + "version": "0.17.6", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.6.tgz", + "integrity": "sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==", "dev": true, "license": "MIT", "dependencies": { @@ -3776,9 +3776,9 @@ } }, "node_modules/alpinejs": { - "version": "3.15.0", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.0.tgz", - "integrity": "sha512-lpokA5okCF1BKh10LG8YjqhfpxyHBk4gE7boIgVHltJzYoM7O9nK3M7VlntLEJGsVmu7U/RzUWajmHREGT38Eg==", + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.15.1.tgz", + "integrity": "sha512-HLO1TtiE92VajFHtLLPK8BWaK1YepV/uj31UrfoGnQ00lyFOJZ+oVY3F0DghPAwvg8sLU79pmjGQSytERa2gEg==", "license": "MIT", "dependencies": { "@vue/reactivity": "~3.1.1" @@ -3963,9 +3963,9 @@ } }, "node_modules/axios": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", - "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.1.tgz", + "integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==", "dev": true, "license": "MIT", "dependencies": { @@ -4075,9 +4075,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.12.tgz", - "integrity": "sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==", + "version": "2.8.21", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.21.tgz", + "integrity": "sha512-JU0h5APyQNsHOlAM7HnQnPToSDQoEBZqzu/YBlqDnEeymPnZDREeXJA3KBMQee+dKteAxZ2AtvQEvVYdZf241Q==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4360,9 +4360,9 @@ } }, "node_modules/browserslist": { - "version": "4.26.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.26.3.tgz", - "integrity": "sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==", + "version": "4.27.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", + "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", "dev": true, "funding": [ { @@ -4380,11 +4380,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.8.9", - "caniuse-lite": "^1.0.30001746", - "electron-to-chromium": "^1.5.227", - "node-releases": "^2.0.21", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.8.19", + "caniuse-lite": "^1.0.30001751", + "electron-to-chromium": "^1.5.238", + "node-releases": "^2.0.26", + "update-browserslist-db": "^1.1.4" }, "bin": { "browserslist": "cli.js" @@ -4521,9 +4521,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001748", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001748.tgz", - "integrity": "sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==", + "version": "1.0.30001752", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001752.tgz", + "integrity": "sha512-vKUk7beoukxE47P5gcVNKkDRzXdVofotshHwfR9vmpeFKxmI5PBpgOMC18LUJUA/DvJ70Y7RveasIBraqsyO/g==", "dev": true, "funding": [ { @@ -4569,9 +4569,9 @@ } }, "node_modules/chart.js": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", - "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", + "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", "license": "MIT", "dependencies": { "@kurkle/color": "^0.3.0" @@ -4966,13 +4966,13 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.45.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.45.1.tgz", - "integrity": "sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==", + "version": "3.46.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.46.0.tgz", + "integrity": "sha512-p9hObIIEENxSV8xIu+V68JjSeARg6UVMG5mR+JEUguG3sI6MsiS1njz2jHmyJDvA+8jX/sytkBHup6kxhM9law==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.25.3" + "browserslist": "^4.26.3" }, "funding": { "type": "opencollective", @@ -5736,9 +5736,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.232", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.232.tgz", - "integrity": "sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==", + "version": "1.5.244", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.244.tgz", + "integrity": "sha512-OszpBN7xZX4vWMPJwB9illkN/znA8M36GQqQxi6MNy9axWxhOfJyZZJtSLQCpEFLHP2xK33BiWx9aIuIEXVCcw==", "dev": true, "license": "ISC" }, @@ -5820,9 +5820,9 @@ } }, "node_modules/envinfo": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.17.0.tgz", - "integrity": "sha512-GpfViocsFM7viwClFgxK26OtjMlKN67GCR5v6ASFkotxtpBWd9d+vNy+AH7F2E1TUkMDZ8P/dDPZX71/NG8xnQ==", + "version": "7.19.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.19.0.tgz", + "integrity": "sha512-DoSM9VyG6O3vqBf+p3Gjgr/Q52HYBBtO3v+4koAxt1MnWr+zEnxE+nke/yXS4lt2P4SYCHQ4V3f1i88LQVOpAw==", "dev": true, "license": "MIT", "bin": { @@ -5899,9 +5899,9 @@ } }, "node_modules/esbuild": { - "version": "0.25.10", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", - "integrity": "sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==", + "version": "0.25.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", + "integrity": "sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -5912,32 +5912,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.10", - "@esbuild/android-arm": "0.25.10", - "@esbuild/android-arm64": "0.25.10", - "@esbuild/android-x64": "0.25.10", - "@esbuild/darwin-arm64": "0.25.10", - "@esbuild/darwin-x64": "0.25.10", - "@esbuild/freebsd-arm64": "0.25.10", - "@esbuild/freebsd-x64": "0.25.10", - "@esbuild/linux-arm": "0.25.10", - "@esbuild/linux-arm64": "0.25.10", - "@esbuild/linux-ia32": "0.25.10", - "@esbuild/linux-loong64": "0.25.10", - "@esbuild/linux-mips64el": "0.25.10", - "@esbuild/linux-ppc64": "0.25.10", - "@esbuild/linux-riscv64": "0.25.10", - "@esbuild/linux-s390x": "0.25.10", - "@esbuild/linux-x64": "0.25.10", - "@esbuild/netbsd-arm64": "0.25.10", - "@esbuild/netbsd-x64": "0.25.10", - "@esbuild/openbsd-arm64": "0.25.10", - "@esbuild/openbsd-x64": "0.25.10", - "@esbuild/openharmony-arm64": "0.25.10", - "@esbuild/sunos-x64": "0.25.10", - "@esbuild/win32-arm64": "0.25.10", - "@esbuild/win32-ia32": "0.25.10", - "@esbuild/win32-x64": "0.25.10" + "@esbuild/aix-ppc64": "0.25.11", + "@esbuild/android-arm": "0.25.11", + "@esbuild/android-arm64": "0.25.11", + "@esbuild/android-x64": "0.25.11", + "@esbuild/darwin-arm64": "0.25.11", + "@esbuild/darwin-x64": "0.25.11", + "@esbuild/freebsd-arm64": "0.25.11", + "@esbuild/freebsd-x64": "0.25.11", + "@esbuild/linux-arm": "0.25.11", + "@esbuild/linux-arm64": "0.25.11", + "@esbuild/linux-ia32": "0.25.11", + "@esbuild/linux-loong64": "0.25.11", + "@esbuild/linux-mips64el": "0.25.11", + "@esbuild/linux-ppc64": "0.25.11", + "@esbuild/linux-riscv64": "0.25.11", + "@esbuild/linux-s390x": "0.25.11", + "@esbuild/linux-x64": "0.25.11", + "@esbuild/netbsd-arm64": "0.25.11", + "@esbuild/netbsd-x64": "0.25.11", + "@esbuild/openbsd-arm64": "0.25.11", + "@esbuild/openbsd-x64": "0.25.11", + "@esbuild/openharmony-arm64": "0.25.11", + "@esbuild/sunos-x64": "0.25.11", + "@esbuild/win32-arm64": "0.25.11", + "@esbuild/win32-ia32": "0.25.11", + "@esbuild/win32-x64": "0.25.11" } }, "node_modules/escalade": { @@ -7088,9 +7088,9 @@ } }, "node_modules/i18next": { - "version": "25.5.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.5.3.tgz", - "integrity": "sha512-joFqorDeQ6YpIXni944upwnuHBf5IoPMuqAchGVeQLdWC2JOjxgM9V8UGLhNIIH/Q8QleRxIi0BSRQehSrDLcg==", + "version": "25.6.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.6.0.tgz", + "integrity": "sha512-tTn8fLrwBYtnclpL5aPXK/tAYBLWVvoHM1zdfXoRNLcI+RvtMsoZRV98ePlaW3khHYKuNh/Q65W/+NVFUeIwVw==", "funding": [ { "type": "individual", @@ -7266,9 +7266,9 @@ } }, "node_modules/immutable": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz", - "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz", + "integrity": "sha512-p6u1bG3YSnINT5RQmx/yRZBpenIl30kVxkTLDyHLIMk0gict704Q9n+thfDI7lTRm9vXdDYutVzXhzcThxTnXA==", "dev": true, "license": "MIT" }, @@ -7874,9 +7874,9 @@ } }, "node_modules/launch-editor": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.11.1.tgz", - "integrity": "sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.12.0.tgz", + "integrity": "sha512-giOHXoOtifjdHqUamwKq6c49GzBdLjvxrd2D+Q4V6uOHopJv7p9VJxikDsQ/CBXZbEITgUqSVHXLTG3VhPP1Dg==", "dev": true, "license": "MIT", "dependencies": { @@ -7908,13 +7908,17 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", + "integrity": "sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/loader-utils": { @@ -7994,9 +7998,9 @@ } }, "node_modules/magic-string": { - "version": "0.30.19", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.19.tgz", - "integrity": "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==", + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8464,9 +8468,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.23", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.23.tgz", - "integrity": "sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==", + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", "dev": true, "license": "MIT" }, @@ -9984,13 +9988,13 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -10106,9 +10110,9 @@ } }, "node_modules/rollup": { - "version": "4.52.4", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.4.tgz", - "integrity": "sha512-CLEVl+MnPAiKh5pl4dEWSyMTpuflgNQiLGhMv8ezD5W/qP8AKvmYpCOKRRNOh7oRKnauBZ4SyeYkMS+1VSyKwQ==", + "version": "4.52.5", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz", + "integrity": "sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==", "dev": true, "license": "MIT", "dependencies": { @@ -10122,28 +10126,28 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.52.4", - "@rollup/rollup-android-arm64": "4.52.4", - "@rollup/rollup-darwin-arm64": "4.52.4", - "@rollup/rollup-darwin-x64": "4.52.4", - "@rollup/rollup-freebsd-arm64": "4.52.4", - "@rollup/rollup-freebsd-x64": "4.52.4", - "@rollup/rollup-linux-arm-gnueabihf": "4.52.4", - "@rollup/rollup-linux-arm-musleabihf": "4.52.4", - "@rollup/rollup-linux-arm64-gnu": "4.52.4", - "@rollup/rollup-linux-arm64-musl": "4.52.4", - "@rollup/rollup-linux-loong64-gnu": "4.52.4", - "@rollup/rollup-linux-ppc64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-gnu": "4.52.4", - "@rollup/rollup-linux-riscv64-musl": "4.52.4", - "@rollup/rollup-linux-s390x-gnu": "4.52.4", - "@rollup/rollup-linux-x64-gnu": "4.52.4", - "@rollup/rollup-linux-x64-musl": "4.52.4", - "@rollup/rollup-openharmony-arm64": "4.52.4", - "@rollup/rollup-win32-arm64-msvc": "4.52.4", - "@rollup/rollup-win32-ia32-msvc": "4.52.4", - "@rollup/rollup-win32-x64-gnu": "4.52.4", - "@rollup/rollup-win32-x64-msvc": "4.52.4", + "@rollup/rollup-android-arm-eabi": "4.52.5", + "@rollup/rollup-android-arm64": "4.52.5", + "@rollup/rollup-darwin-arm64": "4.52.5", + "@rollup/rollup-darwin-x64": "4.52.5", + "@rollup/rollup-freebsd-arm64": "4.52.5", + "@rollup/rollup-freebsd-x64": "4.52.5", + "@rollup/rollup-linux-arm-gnueabihf": "4.52.5", + "@rollup/rollup-linux-arm-musleabihf": "4.52.5", + "@rollup/rollup-linux-arm64-gnu": "4.52.5", + "@rollup/rollup-linux-arm64-musl": "4.52.5", + "@rollup/rollup-linux-loong64-gnu": "4.52.5", + "@rollup/rollup-linux-ppc64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-gnu": "4.52.5", + "@rollup/rollup-linux-riscv64-musl": "4.52.5", + "@rollup/rollup-linux-s390x-gnu": "4.52.5", + "@rollup/rollup-linux-x64-gnu": "4.52.5", + "@rollup/rollup-linux-x64-musl": "4.52.5", + "@rollup/rollup-openharmony-arm64": "4.52.5", + "@rollup/rollup-win32-arm64-msvc": "4.52.5", + "@rollup/rollup-win32-ia32-msvc": "4.52.5", + "@rollup/rollup-win32-x64-gnu": "4.52.5", + "@rollup/rollup-win32-x64-msvc": "4.52.5", "fsevents": "~2.3.2" } }, @@ -10290,9 +10294,9 @@ } }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "dev": true, "license": "ISC", "bin": { @@ -10784,9 +10788,9 @@ } }, "node_modules/std-env": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", "dev": true, "license": "MIT" }, @@ -11316,9 +11320,9 @@ } }, "node_modules/undici-types": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.14.0.tgz", - "integrity": "sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -11387,9 +11391,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", + "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", "dev": true, "funding": [ { @@ -11513,9 +11517,9 @@ } }, "node_modules/vite": { - "version": "7.1.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.11.tgz", - "integrity": "sha512-uzcxnSDVjAopEUjljkWh8EIrg6tlzrjFUfMcR1EVsRDGwf/ccef0qQPRyOrROwhrTDaApueq+ja+KLPlzR/zdg==", + "version": "7.1.12", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "dev": true, "license": "MIT", "dependencies": { @@ -11836,9 +11840,9 @@ "license": "BSD-2-Clause" }, "node_modules/webpack": { - "version": "5.102.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.0.tgz", - "integrity": "sha512-hUtqAR3ZLVEYDEABdBioQCIqSoguHbFn1K7WlPPWSuXmx0031BD73PSE35jKyftdSh4YLDoQNgK4pqBt5Q82MA==", + "version": "5.102.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.102.1.tgz", + "integrity": "sha512-7h/weGm9d/ywQ6qzJ+Xy+r9n/3qgp/thalBbpOi5i223dPXKi04IBtqPN9nTd+jBc7QKfvDbaBnFipYp4sJAUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -11850,7 +11854,7 @@ "@webassemblyjs/wasm-parser": "^1.14.1", "acorn": "^8.15.0", "acorn-import-phases": "^1.0.3", - "browserslist": "^4.24.5", + "browserslist": "^4.26.3", "chrome-trace-event": "^1.0.2", "enhanced-resolve": "^5.17.3", "es-module-lexer": "^1.2.1", @@ -11862,8 +11866,8 @@ "loader-runner": "^4.2.0", "mime-types": "^2.1.27", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.2.3", + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", "terser-webpack-plugin": "^5.3.11", "watchpack": "^2.4.4", "webpack-sources": "^3.3.3" diff --git a/public/v1/js/ff/charts.defaults.js b/public/v1/js/ff/charts.defaults.js index 4bb308ee26..4bd57b5445 100644 --- a/public/v1/js/ff/charts.defaults.js +++ b/public/v1/js/ff/charts.defaults.js @@ -42,13 +42,11 @@ function formatLabel(str, maxwidth) { if (concat.length > maxwidth) { sections.push(temp); temp = ""; - } - else { + } else { if (index === (words.length - 1)) { sections.push(concat); return; - } - else { + } else { temp = concat; return; } @@ -62,8 +60,7 @@ function formatLabel(str, maxwidth) { if (item.length < maxwidth) { temp = item; - } - else { + } else { sections.push(item); } @@ -98,9 +95,11 @@ var defaultChartOptions = { ticks: { callback: function (tickValue) { "use strict"; + if (anonymous) { + return accounting.formatMoney(0); + } // use first symbol or null: return accounting.formatMoney(tickValue); - }, beginAtZero: true } @@ -112,8 +111,11 @@ var defaultChartOptions = { callbacks: { label: function (tooltipItem, data) { "use strict"; - return data.datasets[tooltipItem.datasetIndex].label + ': ' + - accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol); + var string = accounting.formatMoney(tooltipItem.yLabel, data.datasets[tooltipItem.datasetIndex].currency_symbol); + if (anonymous) { + string = accounting.formatMoney(0); + } + return data.datasets[tooltipItem.datasetIndex].label + ': ' + string; } } } @@ -125,7 +127,11 @@ var pieOptionsWithCurrency = { label: function (tooltipItem, data) { "use strict"; var value = data.datasets[0].data[tooltipItem.index]; - return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]); + var string = accounting.formatMoney(value, data.datasets[tooltipItem.datasetIndex].currency_symbol[tooltipItem.index]); + if (anonymous) { + string = accounting.formatMoney(0); + } + return data.labels[tooltipItem.index] + ': ' + string; } } }, @@ -139,7 +145,11 @@ var defaultPieOptions = { label: function (tooltipItem, data) { "use strict"; var value = data.datasets[0].data[tooltipItem.index]; - return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value); + var string = accounting.formatMoney(value); + if (anonymous) { + string = accounting.formatMoney(0); + } + return data.labels[tooltipItem.index] + ': ' + string; } } }, @@ -153,7 +163,11 @@ var neutralDefaultPieOptions = { label: function (tooltipItem, data) { "use strict"; var value = data.datasets[0].data[tooltipItem.index]; - return data.labels[tooltipItem.index] + ': ' + accounting.formatMoney(value, '¤'); + var string = accounting.formatMoney(value, '¤'); + if(anonymous) { + string = accounting.formatMoney(0); + } + return data.labels[tooltipItem.index] + ': ' + string; } } }, diff --git a/public/v1/js/ff/firefly.js b/public/v1/js/ff/firefly.js index 442fd820d1..846971fb5b 100644 --- a/public/v1/js/ff/firefly.js +++ b/public/v1/js/ff/firefly.js @@ -47,7 +47,6 @@ function parseToLocalDates() { $(function () { "use strict"; - configAccounting(currencySymbol); // on submit of logout button: diff --git a/public/v1/js/ff/help.js b/public/v1/js/ff/help.js index 36998bbfea..30c0f76c0f 100644 --- a/public/v1/js/ff/help.js +++ b/public/v1/js/ff/help.js @@ -17,13 +17,42 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ -/** global: token, helpPageTitle */ +/** global: token, helpPageTitle, anonymous */ $(function () { "use strict"; $('#help').click(showHelp); + $('#anonymous').click(changeAnonymity) }); +function submitAnonymity(value) { + $.ajax({ + url: 'api/v1/preferences/anonymous', + data: JSON.stringify({data: value}), + type: 'PUT', + headers: { + 'Content-Type': 'application/json', + 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content'), + }, + }); +} + +function changeAnonymity(e) { + if (anonymous) { + console.log('Will DISABLE.'); + submitAnonymity(false); + alert(anonymous_warning_off_txt); + window.location.reload(true); + } + if (!anonymous) { + console.log('Will ENABLE.'); + submitAnonymity(true); + alert(anonymous_warning_on_txt); + window.location.reload(true); + } + return false; +} + function showHelp(e) { "use strict"; var target = $(e.target); diff --git a/resources/assets/v1/src/components/exchange-rates/Rates.vue b/resources/assets/v1/src/components/exchange-rates/Rates.vue index 0ac13a94ad..169d59336e 100644 --- a/resources/assets/v1/src/components/exchange-rates/Rates.vue +++ b/resources/assets/v1/src/components/exchange-rates/Rates.vue @@ -44,20 +44,29 @@
@@ -168,16 +189,19 @@
- -

+

@@ -217,9 +241,9 @@ export default { mounted() { // get from and to code from URL this.newDate = format(new Date, 'yyyy-MM-dd'); - let parts = window.location.href.split('/'); - this.from_code = parts[parts.length - 2]; - this.to_code = parts[parts.length - 1]; + let parts = window.location.pathname.split('/'); + this.from_code = parts[parts.length - 2].toUpperCase(); + this.to_code = parts[parts.length - 1].toUpperCase(); const params = new Proxy(new URLSearchParams(window.location.search), { get: (searchParams, prop) => searchParams.get(prop), @@ -232,8 +256,8 @@ export default { this.downloadRates(this.page); }, methods: { - submitRate: function(e) { - if(e) e.preventDefault(); + submitRate: function (e) { + if (e) e.preventDefault(); this.posting = true; axios.post("./api/v1/exchange-rates", { @@ -260,33 +284,74 @@ export default { if (0 === parts.length) { return; } + console.log('These are the parts', parts); if ('' !== this.rates[index].rate) { + //console.log('[a] Rate info is', this.rates[index]); this.updating = true; - axios.put("./api/v1/exchange-rates/" + this.rates[index].rate_id, {rate: this.rates[index].rate}) - .then(() => { - this.updating = false; - }); + if (0 === parseInt(this.rates[index].rate_id)) { + console.log('[a] POST, not PUT.'); + axios.post('./api/v1/exchange-rates', + { + from: this.from_code, + to: this.to_code, + rate: this.rates[index].rate, + date: this.rates[index].date_field + }) + .then(() => { + this.updating = false; + }); + } + if (0 !== parseInt(this.rates[index].rate_id)) { + console.log('[a] PUT, not POST.'); + axios.put('./api/v1/exchange-rates/' + this.rates[index].rate_id, {rate: this.rates[index].rate}) + .then(() => { + this.updating = false; + }); + } } if ('' !== this.rates[index].inverse) { + //console.log('[b] Rate info is', this.rates[index]); this.updating = true; - axios.put("./api/v1/exchange-rates/" + this.rates[index].inverse_id, {rate: this.rates[index].inverse}) - .then(() => { - this.updating = false; - }); + if (0 === parseInt(this.rates[index].inverse_id)) { + console.log('[b] POST, not PUT.'); + // post, not put + axios.post('./api/v1/exchange-rates', + { + // remember, this is in reverse. + from: this.to_code, + to: this.from_code, + rate: this.rates[index].inverse, + date: this.rates[index].date_field + }) + .then(() => { + this.updating = false; + }); + } + if (0 !== parseInt(this.rates[index].inverse_id)) { + console.log('[b] PUT, not POST.'); + axios.put('./api/v1/exchange-rates/' + this.rates[index].inverse_id, {rate: this.rates[index].inverse}) + .then(() => { + this.updating = false; + }); + } } }, deleteRate: function (index) { // console.log(this.rates[index].key); let parts = this.spliceKey(this.rates[index].key); + if (0 === parts.length) { return; } - // console.log(parts); - + let rateId = parseInt(this.rates[index].rate_id); + let inverseId = parseInt(this.rates[index].inverse_id); // delete A to B - axios.delete("./api/v1/exchange-rates/" + parts.from + '/' + parts.to + '/' + format(parts.date, 'yyyy-MM-dd')); - // delete B to A. - axios.delete("./api/v1/exchange-rates/" + parts.to + '/' + parts.from + '/' + format(parts.date, 'yyyy-MM-dd')); + if (rateId > 0) { + axios.delete('./api/v1/exchange-rates/' + rateId); + } + if (inverseId > 0) { + axios.delete('./api/v1/exchange-rates/' + inverseId); + } this.rates.splice(index, 1); }, @@ -307,6 +372,7 @@ export default { }; }, downloadCurrencies: function () { + console.log('Now downloading currencies.'); this.loading = true; axios.get("./api/v1/currencies/" + this.from_code).then((response) => { this.from = { @@ -327,31 +393,36 @@ export default { downloadRates: function (page) { this.tempRates = {}; this.loading = true; - axios.get("./api/v1/exchange-rates/" + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => { + console.log('Now downloading rates.', page); + axios.get('./api/v1/exchange-rates/' + this.from_code + '/' + this.to_code + '?page=' + page).then((response) => { for (let i in response.data.data) { if (response.data.data.hasOwnProperty(i)) { + console.log('Downloaded entry #' + i); let current = response.data.data[i]; let date = new Date(current.attributes.date); - let from_code = current.attributes.from_currency_code; - let to_code = current.attributes.to_currency_code; + let from_code = current.attributes.from_currency_code.toUpperCase(); + let to_code = current.attributes.to_currency_code.toUpperCase(); let rate = current.attributes.rate; let inverse = ''; let rate_id = current.id; let inverse_id = '0'; let key = from_code + '_' + to_code + '_' + format(date, 'yyyy-MM-dd'); - // console.log('Key is now "' + key + '"'); + console.log('Key is now "' + key + '"'); // perhaps the returned rate is actually the inverse rate. if (from_code === this.to_code && to_code === this.from_code) { // console.log('Inverse rate found!'); key = to_code + '_' + from_code + '_' + format(date, 'yyyy-MM-dd'); rate = ''; + // new: set rate id to zero. + rate_id = '0'; inverse = current.attributes.rate; inverse_id = current.id; - // console.log('Key updated to "' + key + '"'); + console.log('Key updated to "' + key + '"'); } if (!this.tempRates.hasOwnProperty(key)) { + console.log('New entry stored'); this.tempRates[key] = { key: key, date: date, @@ -374,6 +445,7 @@ export default { this.tempRates[key].rate = rate; this.tempRates[key].rate_id = rate_id; } + console.log('Found exchange rate #' + this.tempRates[key].rate_id + ' with inverse #' + this.tempRates[key].inverse_id); } diff --git a/resources/lang/en_US/demo.php b/resources/lang/en_US/demo.php index 148b699ed4..4730528f79 100644 --- a/resources/lang/en_US/demo.php +++ b/resources/lang/en_US/demo.php @@ -35,5 +35,5 @@ return [ 'currencies-index' => 'Firefly III supports multiple currencies. Although it defaults to the Euro it can be set to the US Dollar and many other currencies. As you can see a small selection of currencies has been included but you can add your own if you wish to. Changing the default currency will not change the currency of existing transactions however: Firefly III supports the use of multiple currencies at the same time.', 'transactions-index' => 'These expenses, deposits and transfers are not particularly imaginative. They have been generated automatically.', 'piggy-banks-index' => 'As you can see, there are three piggy banks. Use the plus and minus buttons to influence the amount of money in each piggy bank. Click the name of the piggy bank to see the administration for each piggy bank.', - 'profile-index' => 'Keep in mind that the demo site resets every four hours. Your access may be revoked at any time. This happens automatically and is not a bug.', + 'profile-index' => 'Keep in mind that the demo site resets every four hours. Standard user agents (curl, Postman, wget) are blocked. Your access may be revoked at any time. This happens automatically and is not a bug.', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index f530ea76e0..645560d033 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -235,6 +235,8 @@ return [ 'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', 'here_be_dragons' => 'Hic sunt dracones', 'bad_date_transaction' => 'Firefly III has detected you have transactions from before the year 1970. Please correct these transactions at your earliest convenience.', + 'anonymous_warning_on' => 'For your privacy, all amounts are now displayed as "zero". Warning: text input boxes may still show the original amounts!', + 'anonymous_warning_off' => 'Amounts will be visible again. Please be mindful of your surroundings.', // Webhooks 'webhooks' => 'Webhooks', @@ -823,6 +825,8 @@ return [ 'apply_rule_group_selection' => 'Apply rule group ":title" to a selection of your transactions', 'apply_rule_group_selection_intro' => 'Rule groups like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run all the rules in this group on a selection of your existing transactions. This can be useful when you have updated a group of rules and you need the changes to be applied to all of your other transactions.', 'applied_rule_group_selection' => 'Rule group ":title" has been applied to your selection.', + 'rule_run_after_creation' => 'If you check this box, you get the opportunity to run the rule after it has been created.', + 'rule_run_after_edit' => 'If you check this box, you get the opportunity to run the rule after it has been updated.', // actions and triggers 'rule_trigger_store_journal' => 'When a transaction is created', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index aec4ec5a28..38960dfc44 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -66,6 +66,7 @@ return [ 'opening_balance' => 'Opening balance', 'tag_mode' => 'Tag mode', 'virtual_balance' => 'Virtual balance', + 'run_after_form' => 'Run this rule', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index f7b27c0da6..008eee28b2 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -24,6 +24,7 @@ declare(strict_types=1); return [ + 'invalid_account_list' => 'Invalid account type list', 'limit_exists' => 'There is already a budget limit (amount) for this budget and currency in the given period.', 'invalid_sort_instruction' => 'The sort instruction is invalid for an object of type ":object".', 'invalid_sort_instruction_index' => 'The sort instruction at index #:index is invalid for an object of type ":object".', @@ -32,6 +33,7 @@ return [ 'webhook_account_info' => 'Cannot deliver account information for budget related webhooks.', 'webhook_transaction_info' => 'Cannot deliver transaction information for budget related webhooks.', 'invalid_account_type' => 'A piggy bank can only be linked to asset accounts and liabilities', + 'unique_currency_code' => 'This currency code is already in use', 'invalid_account_currency' => 'This account does not use the currency you have selected', 'current_amount_too_much' => 'The combined amount in "current_amount" cannot exceed the "target_amount".', 'filter_must_be_in' => 'Filter ":filter" must be one of: :values', diff --git a/resources/views/accounts/create.twig b/resources/views/accounts/create.twig index c52e127687..36ddda0c9a 100644 --- a/resources/views/accounts/create.twig +++ b/resources/views/accounts/create.twig @@ -90,15 +90,15 @@ var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; - - - + + {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/accounts/edit.twig b/resources/views/accounts/edit.twig index e5cbdb0266..d8a9b5226f 100644 --- a/resources/views/accounts/edit.twig +++ b/resources/views/accounts/edit.twig @@ -122,15 +122,15 @@ var iAmOwed = '{{ 'i_am_owed_amount'|_|escape('js') }}'; var iOwe = '{{ 'i_owe_amount'|_|escape('js') }}'; - - - + + {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/accounts/index.twig b/resources/views/accounts/index.twig index e8e12dd7a8..213aacf9be 100644 --- a/resources/views/accounts/index.twig +++ b/resources/views/accounts/index.twig @@ -90,6 +90,6 @@ - - + + {% endblock %} diff --git a/resources/views/accounts/reconcile/index.twig b/resources/views/accounts/reconcile/index.twig index 5cef87c7af..4ed4a2ae88 100644 --- a/resources/views/accounts/reconcile/index.twig +++ b/resources/views/accounts/reconcile/index.twig @@ -133,5 +133,5 @@ var indexUrl = '{{ indexUrl }}'; var selectRangeAndBalance = '{{ 'select_range_and_balance'|_|escape('js') }}'; - + {% endblock %} diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index 6a79e5fbbc..190d0f630b 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -219,25 +219,25 @@ {% if location %} - + {% endif %} - - - - + + - - + - + {# required for groups.twig #} - + {% endblock %} {% block styles %} {% if location %} - + {% endif %} {% endblock %} diff --git a/resources/views/attachments/index.twig b/resources/views/attachments/index.twig index 307c3f1823..28e67eb014 100644 --- a/resources/views/attachments/index.twig +++ b/resources/views/attachments/index.twig @@ -68,10 +68,10 @@ {% endblock %} {% block scripts %} - + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/bills/create.twig b/resources/views/bills/create.twig index cdab3ccef1..46342c9120 100644 --- a/resources/views/bills/create.twig +++ b/resources/views/bills/create.twig @@ -68,17 +68,17 @@ {% endblock %} {% block styles %} - - - + + + {% endblock %} {% block scripts %} - - - - + + + + {# auto complete for object groups #} - - + + {% endblock %} diff --git a/resources/views/bills/edit.twig b/resources/views/bills/edit.twig index 4620647039..9e2c3e33a5 100644 --- a/resources/views/bills/edit.twig +++ b/resources/views/bills/edit.twig @@ -74,24 +74,24 @@ {% endblock %} {% block styles %} - - - {% endblock %} {% block scripts %} - - - - + + {# auto complete for object groups #} - - {% endblock %} diff --git a/resources/views/bills/index.twig b/resources/views/bills/index.twig index 49a2905d5d..abb3a8f454 100644 --- a/resources/views/bills/index.twig +++ b/resources/views/bills/index.twig @@ -41,6 +41,6 @@ {% endblock %} {% block scripts %} - - + + {% endblock %} diff --git a/resources/views/bills/show.twig b/resources/views/bills/show.twig index ec55e97220..84b4c347bb 100644 --- a/resources/views/bills/show.twig +++ b/resources/views/bills/show.twig @@ -188,10 +188,10 @@ var billCurrencySymbol = "{{ convertToPrimary ? primaryCurrency.symbol : object.data.currency.symbol }}"; var billUrl = '{{ route('chart.bill.single', [object.data.id]) }}'; - - - - + + + + {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/budgets/create.twig b/resources/views/budgets/create.twig index fd1e014b48..576eca9665 100644 --- a/resources/views/budgets/create.twig +++ b/resources/views/budgets/create.twig @@ -60,5 +60,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/budgets/edit.twig b/resources/views/budgets/edit.twig index ebe8d604b7..5f78e91ebc 100644 --- a/resources/views/budgets/edit.twig +++ b/resources/views/budgets/edit.twig @@ -61,5 +61,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index 73b8814968..014742cc74 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -267,9 +267,14 @@
{{ primaryCurrency.symbol }}
+ {% if not anonymous %} + {% endif %} + {% if anonymous %} + --- + {% endif %}
@@ -283,10 +288,15 @@ {% endif %}
{{ budgetLimit.currency_symbol }}
+ {% if not anonymous %} + {% endif %} + {% if anonymous %} + --- + {% endif %}
@@ -455,11 +465,11 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} - + - - + + {% endblock %} diff --git a/resources/views/budgets/no-budget.twig b/resources/views/budgets/no-budget.twig index ac5fcf44cc..3b48d8ed79 100644 --- a/resources/views/budgets/no-budget.twig +++ b/resources/views/budgets/no-budget.twig @@ -60,5 +60,5 @@ {% endblock %} {% block scripts %} {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/budgets/show.twig b/resources/views/budgets/show.twig index 8f3c53aefa..5b2915d575 100644 --- a/resources/views/budgets/show.twig +++ b/resources/views/budgets/show.twig @@ -230,10 +230,10 @@ {% endif %} - - - - + + + + {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/categories/create.twig b/resources/views/categories/create.twig index 0556ac39ba..1301ef0f45 100644 --- a/resources/views/categories/create.twig +++ b/resources/views/categories/create.twig @@ -53,5 +53,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/categories/edit.twig b/resources/views/categories/edit.twig index aa9b6263d6..8d9e7b863a 100644 --- a/resources/views/categories/edit.twig +++ b/resources/views/categories/edit.twig @@ -59,5 +59,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/categories/index.twig b/resources/views/categories/index.twig index cefb5c0df2..e4e19944ac 100644 --- a/resources/views/categories/index.twig +++ b/resources/views/categories/index.twig @@ -40,10 +40,10 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} - - + + {% endblock %} diff --git a/resources/views/categories/no-category.twig b/resources/views/categories/no-category.twig index 7dde51d679..e616ae72cb 100644 --- a/resources/views/categories/no-category.twig +++ b/resources/views/categories/no-category.twig @@ -60,5 +60,5 @@ {% endblock %} {% block scripts %} {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/categories/show.twig b/resources/views/categories/show.twig index 5a8563356e..b0b0788130 100644 --- a/resources/views/categories/show.twig +++ b/resources/views/categories/show.twig @@ -113,10 +113,10 @@ var everything = '{{ route('chart.category.all', [category.id]) }}'; var specific = '{{ route('chart.category.specific', [category.id, start.format('Ymd')]) }}'; - - - - + + + + {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/currencies/index.twig b/resources/views/currencies/index.twig index c05c82ea20..abe1ea20e5 100644 --- a/resources/views/currencies/index.twig +++ b/resources/views/currencies/index.twig @@ -98,5 +98,5 @@ var redirectUrl = "{{ route('currencies.index') }}"; var updateCurrencyUrl = "{{ route('api.v1.currencies.update', ['']) }}"; - + {% endblock %} diff --git a/resources/views/index.twig b/resources/views/index.twig index ec0655f809..58ad2587cb 100644 --- a/resources/views/index.twig +++ b/resources/views/index.twig @@ -211,14 +211,14 @@ {% endif %} - - - - - + + {% endblock %} {% block styles %} {% endblock %} diff --git a/resources/views/javascript/variables.twig b/resources/views/javascript/variables.twig index 37c6ab8c13..68714d7537 100644 --- a/resources/views/javascript/variables.twig +++ b/resources/views/javascript/variables.twig @@ -26,6 +26,7 @@ ranges: ranges }; var uid = "{{ uid }}"; +var anonymous = {{ anonymous }}; var language = "{{ language|escape }}"; var locale = "{{ locale|escape }}"; var currencyCode = '{{ currencyCode|escape('js') }}'; @@ -51,6 +52,9 @@ var acc_config_new = {format: accountingConfig}; var helpPageTitle = "{{ trans('firefly.help_for_this_page')|escape('js') }}"; var helpPageBody = "{{ trans('firefly.help_for_this_page_body')|escape('js') }}"; +var anonymous_warning_on_txt = "{{ trans('firefly.anonymous_warning_on')|escape('js') }}"; +var anonymous_warning_off_txt= "{{ trans('firefly.anonymous_warning_off')|escape('js') }}"; + var edit_selected_txt = "{{ trans('firefly.mass_edit')|escape('js') }}"; var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}"; var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}"; diff --git a/resources/views/layout/default.twig b/resources/views/layout/default.twig index a20ed1b060..11112c6e64 100644 --- a/resources/views/layout/default.twig +++ b/resources/views/layout/default.twig @@ -29,14 +29,14 @@ {# CSS things #} - - {# the theme #} - {% if 'browser' == darkMode %} - - - {% endif %} {% if 'dark' == darkMode %} - - {% endif %} {% if 'light' == darkMode %} - - {% endif %} {# Firefly III customisations #} - + {# Extra CSS for the demo: #} {% if not shownDemo %} - {% endif %} {# Any local custom CSS. #} {% block styles %}{% endblock %} {# this entry is in the header so it's loaded early #} @@ -111,7 +111,16 @@ - + {% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %} diff --git a/resources/views/layout/guest.twig b/resources/views/layout/guest.twig index 889a241aa5..87717a72ba 100644 --- a/resources/views/layout/guest.twig +++ b/resources/views/layout/guest.twig @@ -24,11 +24,11 @@ {# CSS things #} {# libraries #} - - + + {# the theme #} - + {% if 'browser' == darkMode %} - - + + {% endif %} {% if 'dark' == darkMode %} - + {% endif %} {% if 'light' == darkMode %} - + {% endif %} {# Firefly III customisations #} - + {# favicons #} @@ -75,8 +75,8 @@ Developed by James Cole, the source code is licensed under the AGPL-3.0-or-later.
- - + + {% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %} diff --git a/resources/views/layout/install.twig b/resources/views/layout/install.twig index 29e7bc88a8..7693e2265d 100644 --- a/resources/views/layout/install.twig +++ b/resources/views/layout/install.twig @@ -35,8 +35,8 @@ Developed by James Cole, the source code is licensed under the AGPL-3.0-or-later.
- - + + {% block scripts %}{% endblock %} {% if config('firefly.tracker_site_id') != '' and config('firefly.tracker_url') != '' %} diff --git a/resources/views/layout/v3/session.twig b/resources/views/layout/v3/session.twig index 33d893678e..87f52d5937 100644 --- a/resources/views/layout/v3/session.twig +++ b/resources/views/layout/v3/session.twig @@ -7,10 +7,10 @@ {{ 'login_page_title'|_ }} - - - - + + + + {# favicons #} {% include 'partials.favicons' %} @@ -19,11 +19,11 @@ {% block content %}{% endblock %} - + - + - + {% block scripts %} {% endblock %} diff --git a/resources/views/object-groups/edit.twig b/resources/views/object-groups/edit.twig index d6d52e29c6..d64e4645a0 100644 --- a/resources/views/object-groups/edit.twig +++ b/resources/views/object-groups/edit.twig @@ -47,21 +47,21 @@ {% endblock %} {% block scripts %} - - - + {# auto complete for object groups #} - - {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/object-groups/index.twig b/resources/views/object-groups/index.twig index 80402c7423..aee4a9d578 100644 --- a/resources/views/object-groups/index.twig +++ b/resources/views/object-groups/index.twig @@ -70,8 +70,8 @@ {% endblock %} {% block scripts %} - - + + {% endblock %} {% block styles %} {% endblock %} diff --git a/resources/views/partials/debug-table.twig b/resources/views/partials/debug-table.twig index 2e498a162a..a7404e9fb5 100644 --- a/resources/views/partials/debug-table.twig +++ b/resources/views/partials/debug-table.twig @@ -14,6 +14,10 @@ Firefly III {% if FF_IS_DEVELOP %}{% endif %}{% if not FF_IS_DEVELOP %}v{% endif %}{{ FF_VERSION }} + + Build time + {{ system.build_time_nice }} ({{ system.build_time }}) + {# PHP version + settings #} PHP version diff --git a/resources/views/piggy-banks/create.twig b/resources/views/piggy-banks/create.twig index 851efe5f2b..9171a49fc7 100644 --- a/resources/views/piggy-banks/create.twig +++ b/resources/views/piggy-banks/create.twig @@ -59,16 +59,16 @@ {% endblock %} {% block scripts %} - - - + + + {# auto complete for object groups #} - - + + {% endblock %} {% block styles %} - - + + {% endblock %} diff --git a/resources/views/piggy-banks/edit.twig b/resources/views/piggy-banks/edit.twig index 8489ca8064..2a1e8a8600 100644 --- a/resources/views/piggy-banks/edit.twig +++ b/resources/views/piggy-banks/edit.twig @@ -67,21 +67,21 @@ {% endblock %} {% block scripts %} - - - + {# auto complete for object groups #} - - {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/piggy-banks/index.twig b/resources/views/piggy-banks/index.twig index 7a25c002d4..29b0acb320 100644 --- a/resources/views/piggy-banks/index.twig +++ b/resources/views/piggy-banks/index.twig @@ -75,6 +75,6 @@ {% endif %} {% endblock %} {% block scripts %} - - + + {% endblock %} diff --git a/resources/views/piggy-banks/show.twig b/resources/views/piggy-banks/show.twig index 6a5e58f3da..c36632fed5 100644 --- a/resources/views/piggy-banks/show.twig +++ b/resources/views/piggy-banks/show.twig @@ -162,8 +162,8 @@ var piggyBankID = {{ piggyBank.id }}; - - - - + + + + {% endblock %} diff --git a/resources/views/preferences/index.twig b/resources/views/preferences/index.twig index 13984278d3..2e7895d29d 100644 --- a/resources/views/preferences/index.twig +++ b/resources/views/preferences/index.twig @@ -402,16 +402,16 @@ - - - + {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/recurring/create.twig b/resources/views/recurring/create.twig index 2ae827973d..818093fb44 100644 --- a/resources/views/recurring/create.twig +++ b/resources/views/recurring/create.twig @@ -205,16 +205,16 @@ {% endblock %} {% block scripts %} - - - - - - + - {% endblock %} {% block styles %} - - - - {% endblock %} diff --git a/resources/views/recurring/edit.twig b/resources/views/recurring/edit.twig index 37df0d26b6..eac5847c19 100644 --- a/resources/views/recurring/edit.twig +++ b/resources/views/recurring/edit.twig @@ -204,16 +204,16 @@ {% endblock %} {% block scripts %} - - - - - - + - + {% endblock %} {% block styles %} - - - - {% endblock %} diff --git a/resources/views/recurring/index.twig b/resources/views/recurring/index.twig index a484df4d03..0937b0e2f9 100644 --- a/resources/views/recurring/index.twig +++ b/resources/views/recurring/index.twig @@ -158,9 +158,9 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/recurring/show.twig b/resources/views/recurring/show.twig index b9e2f0893c..ca2d49ee2e 100644 --- a/resources/views/recurring/show.twig +++ b/resources/views/recurring/show.twig @@ -213,13 +213,13 @@ {% endblock %} {% block styles %} - {% endblock %} {% block scripts %} - {# required for groups.twig #} - + {% endblock %} diff --git a/resources/views/reports/audit/report.twig b/resources/views/reports/audit/report.twig index 024f3e6638..4589488ea5 100644 --- a/resources/views/reports/audit/report.twig +++ b/resources/views/reports/audit/report.twig @@ -87,6 +87,6 @@ - - + + {% endblock %} diff --git a/resources/views/reports/budget/month.twig b/resources/views/reports/budget/month.twig index faf3a113b5..4ac028ac98 100644 --- a/resources/views/reports/budget/month.twig +++ b/resources/views/reports/budget/month.twig @@ -149,10 +149,10 @@ {% endblock %} {% block scripts %} - - - - + + + + - - + + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/reports/category/month.twig b/resources/views/reports/category/month.twig index c6a02692f2..99631ead05 100644 --- a/resources/views/reports/category/month.twig +++ b/resources/views/reports/category/month.twig @@ -214,12 +214,12 @@ {% endblock %} {% block scripts %} - - - - + + + + - + - + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/reports/default/month.twig b/resources/views/reports/default/month.twig index 687c45a1d4..346f20d521 100644 --- a/resources/views/reports/default/month.twig +++ b/resources/views/reports/default/month.twig @@ -142,14 +142,14 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} - - - - + + + + - - - + + + {% endblock %} diff --git a/resources/views/reports/default/multi-year.twig b/resources/views/reports/default/multi-year.twig index be76587ad0..fc2e5a00b5 100644 --- a/resources/views/reports/default/multi-year.twig +++ b/resources/views/reports/default/multi-year.twig @@ -185,14 +185,14 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} - - - - + + + + - - - + + + {% endblock %} diff --git a/resources/views/reports/default/year.twig b/resources/views/reports/default/year.twig index a11196ffb5..eb7198d562 100644 --- a/resources/views/reports/default/year.twig +++ b/resources/views/reports/default/year.twig @@ -184,10 +184,10 @@ {% endblock %} {% block scripts %} - - - - + + + + - - - + + + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/reports/double/report.twig b/resources/views/reports/double/report.twig index 602d2b1de2..ca6016fef2 100644 --- a/resources/views/reports/double/report.twig +++ b/resources/views/reports/double/report.twig @@ -192,10 +192,10 @@ {% endblock %} {% block scripts %} - - - - + + + + - - + + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/reports/index.twig b/resources/views/reports/index.twig index 15acad7e56..e8970f3e53 100644 --- a/resources/views/reports/index.twig +++ b/resources/views/reports/index.twig @@ -189,7 +189,7 @@ {% endblock %} {% block styles %} - + {% endblock %} {% block scripts %} @@ -202,7 +202,7 @@ var allSelectedText = "{{ trans('firefly.multi_select_all_selected')|escape('js') }}"; var filterPlaceholder = "{{ trans('firefly.multi_select_filter_placeholder')|escape('js') }}"; - - - + + + {% endblock %} diff --git a/resources/views/reports/tag/month.twig b/resources/views/reports/tag/month.twig index 56ffbf775b..9e06bcb200 100644 --- a/resources/views/reports/tag/month.twig +++ b/resources/views/reports/tag/month.twig @@ -245,12 +245,12 @@ {% endblock %} {% block scripts %} - - - - + + + + - + - + {% endblock %} {% block styles %} - + {% endblock %} diff --git a/resources/views/rules/index.twig b/resources/views/rules/index.twig index 62adc40fd3..6082d40141 100644 --- a/resources/views/rules/index.twig +++ b/resources/views/rules/index.twig @@ -212,6 +212,6 @@ var moveRuleGroupUrl = '{{ route('rule-groups.move') }}'; var duplicateRuleUrl = '{{ route('rules.duplicate') }}'; - - + + {% endblock %} diff --git a/resources/views/rules/rule-group/create.twig b/resources/views/rules/rule-group/create.twig index dd4d212683..69c4311765 100644 --- a/resources/views/rules/rule-group/create.twig +++ b/resources/views/rules/rule-group/create.twig @@ -52,5 +52,5 @@ {% endblock %} {% block scripts %} - + {% endblock %} diff --git a/resources/views/rules/rule-group/edit.twig b/resources/views/rules/rule-group/edit.twig index 4f42533ba0..9707b4bc0b 100644 --- a/resources/views/rules/rule-group/edit.twig +++ b/resources/views/rules/rule-group/edit.twig @@ -60,6 +60,6 @@ {% endblock %} {% block scripts %} - {% endblock %} diff --git a/resources/views/rules/rule-group/select-transactions.twig b/resources/views/rules/rule-group/select-transactions.twig index 81abe48208..b941c19bbd 100644 --- a/resources/views/rules/rule-group/select-transactions.twig +++ b/resources/views/rules/rule-group/select-transactions.twig @@ -24,6 +24,8 @@

+ {{ ExpandedForm.date('start') }} + {{ ExpandedForm.date('end') }} {{ AccountForm.assetAccountCheckList('accounts', {'select_all': true,'class': 'account-checkbox', 'label': trans('firefly.include_transactions_from_accounts') }) }}
@@ -39,16 +41,16 @@ {% endblock %} {% block scripts %} - - - + {% endblock %} {% block styles %} - - {% endblock %} diff --git a/resources/views/rules/rule/create.twig b/resources/views/rules/rule/create.twig index e8a134ee7c..b854a92a63 100644 --- a/resources/views/rules/rule/create.twig +++ b/resources/views/rules/rule/create.twig @@ -132,6 +132,7 @@

{{ 'options'|_ }}

+ {{ ExpandedForm.checkbox('run_after_form',1,null, {helpText: trans('firefly.rule_run_after_creation')}) }} {{ ExpandedForm.optionsList('create','rule') }}
+ {{ ExpandedForm.checkbox('run_after_form',1,null, {helpText: trans('firefly.rule_run_after_edit')}) }} {{ ExpandedForm.optionsList('update','rule') }}