Compare commits

...

28 Commits

Author SHA1 Message Date
github-actions[bot]
158081395b Merge pull request #11583 from firefly-iii/release-1769334926
🤖 Automatically merge the PR into the develop branch.
2026-01-25 10:55:33 +01:00
JC5
34a7fd3ef0 🤖 Auto commit for release 'develop' on 2026-01-25 2026-01-25 10:55:27 +01:00
James Cole
96aafacf43 Fix processing transactions in events. 2026-01-25 10:47:30 +01:00
James Cole
035d599910 Fix processing new transactions. 2026-01-25 10:22:02 +01:00
James Cole
f5a929d72e Clean up a bunch of code and improve transaction store events 2026-01-25 09:02:47 +01:00
James Cole
22b97ce8ef Clean up middleware and kernel files. 2026-01-24 20:36:20 +01:00
James Cole
832019792f Improve listeners 2026-01-24 19:02:18 +01:00
James Cole
76b8ff18b0 Transaction events for #11544 2026-01-24 18:52:07 +01:00
James Cole
8c0a82ac0a Add support for batch submission. 2026-01-24 18:34:49 +01:00
James Cole
7edc386cdd Fix strict types 2026-01-24 16:47:17 +01:00
James Cole
5437d07ec2 Merge branch 'develop' of github.com:firefly-iii/firefly-iii into develop
# Conflicts:
#	app/Providers/EventServiceProvider.php
2026-01-24 16:46:08 +01:00
James Cole
ef3a1401dd Remove more events for #11544 2026-01-24 16:45:28 +01:00
github-actions[bot]
aa6169e314 Merge pull request #11582 from firefly-iii/release-1769259448
🤖 Automatically merge the PR into the develop branch.
2026-01-24 13:57:36 +01:00
JC5
4a4f1ff055 🤖 Auto commit for release 'develop' on 2026-01-24 2026-01-24 13:57:28 +01:00
James Cole
844470bf08 Move more events #11544 2026-01-24 13:52:35 +01:00
James Cole
92e985a9b8 Move more events #11544 2026-01-24 13:44:02 +01:00
James Cole
70e11098af Move more events #11544 2026-01-24 13:33:40 +01:00
James Cole
e76ab21091 Fix #11563 2026-01-24 07:17:19 +01:00
github-actions[bot]
87644923cf Merge pull request #11581 from firefly-iii/release-1769199015
🤖 Automatically merge the PR into the develop branch.
2026-01-23 21:10:26 +01:00
JC5
c46e9519c2 🤖 Auto commit for release 'develop' on 2026-01-23 2026-01-23 21:10:15 +01:00
github-actions[bot]
775933c3e8 Merge pull request #11580 from firefly-iii/release-1769183303
🤖 Automatically merge the PR into the develop branch.
2026-01-23 16:48:32 +01:00
JC5
5e316a1f05 🤖 Auto commit for release 'develop' on 2026-01-23 2026-01-23 16:48:23 +01:00
github-actions[bot]
857fe8ed76 Merge pull request #11579 from firefly-iii/release-1769181522
🤖 Automatically merge the PR into the develop branch.
2026-01-23 16:18:50 +01:00
JC5
2cca32d021 🤖 Auto commit for release 'develop' on 2026-01-23 2026-01-23 16:18:42 +01:00
James Cole
09c4f4702d Missing return statement 2026-01-23 16:13:33 +01:00
James Cole
a44f0f362f Catch on user login. 2026-01-23 16:12:49 +01:00
James Cole
b147c5abc6 Merge branch 'main' into develop 2026-01-23 15:16:48 +01:00
James Cole
099e60a2fa Do format. 2026-01-23 15:16:41 +01:00
173 changed files with 1703 additions and 1008 deletions

View File

@@ -402,16 +402,16 @@
},
{
"name": "friendsofphp/php-cs-fixer",
"version": "v3.92.5",
"version": "v3.93.0",
"source": {
"type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58"
"reference": "50895a07cface1385082e4caa6a6786c4e033468"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58",
"reference": "260cc8c4a1d2f6d2f22cd4f9c70aa72e55ebac58",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/50895a07cface1385082e4caa6a6786c4e033468",
"reference": "50895a07cface1385082e4caa6a6786c4e033468",
"shasum": ""
},
"require": {
@@ -443,14 +443,14 @@
},
"require-dev": {
"facile-it/paraunit": "^1.3.1 || ^2.7",
"infection/infection": "^0.31",
"infection/infection": "^0.32",
"justinrainbow/json-schema": "^6.6",
"keradus/cli-executor": "^2.3",
"mikey179/vfsstream": "^1.6.12",
"php-coveralls/php-coveralls": "^2.9",
"php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.6",
"php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.6",
"phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.46",
"phpunit/phpunit": "^9.6.31 || ^10.5.60 || ^11.5.48",
"symfony/polyfill-php85": "^1.33",
"symfony/var-dumper": "^5.4.48 || ^6.4.26 || ^7.4.0 || ^8.0",
"symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0"
@@ -494,7 +494,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.92.5"
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.93.0"
},
"funding": [
{
@@ -502,7 +502,7 @@
"type": "github"
}
],
"time": "2026-01-08T21:57:37+00:00"
"time": "2026-01-23T17:33:21+00:00"
},
{
"name": "psr/container",
@@ -1252,16 +1252,16 @@
},
{
"name": "symfony/console",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587"
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"reference": "6145b304a5c1ea0bdbd0b04d297a5864f9a7d587",
"url": "https://api.github.com/repos/symfony/console/zipball/ace03c4cf9805080ff40cbeec69fca180c339a3b",
"reference": "ace03c4cf9805080ff40cbeec69fca180c339a3b",
"shasum": ""
},
"require": {
@@ -1318,7 +1318,7 @@
"terminal"
],
"support": {
"source": "https://github.com/symfony/console/tree/v8.0.3"
"source": "https://github.com/symfony/console/tree/v8.0.4"
},
"funding": [
{
@@ -1338,7 +1338,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-13T13:06:50+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -1409,16 +1409,16 @@
},
{
"name": "symfony/event-dispatcher",
"version": "v8.0.0",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "573f95783a2ec6e38752979db139f09fec033f03"
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/573f95783a2ec6e38752979db139f09fec033f03",
"reference": "573f95783a2ec6e38752979db139f09fec033f03",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/99301401da182b6cfaa4700dbe9987bb75474b47",
"reference": "99301401da182b6cfaa4700dbe9987bb75474b47",
"shasum": ""
},
"require": {
@@ -1470,7 +1470,7 @@
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.0"
"source": "https://github.com/symfony/event-dispatcher/tree/v8.0.4"
},
"funding": [
{
@@ -1490,7 +1490,7 @@
"type": "tidelift"
}
],
"time": "2025-10-30T14:17:19+00:00"
"time": "2026-01-05T11:45:55+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
@@ -1640,16 +1640,16 @@
},
{
"name": "symfony/finder",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/finder.git",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12"
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"reference": "dd3a2953570a283a2ba4e17063bb98c734cf5b12",
"url": "https://api.github.com/repos/symfony/finder/zipball/42e48eb02e07d5f3771d194d67da117eb824c8c1",
"reference": "42e48eb02e07d5f3771d194d67da117eb824c8c1",
"shasum": ""
},
"require": {
@@ -1684,7 +1684,7 @@
"description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/finder/tree/v8.0.3"
"source": "https://github.com/symfony/finder/tree/v8.0.4"
},
"funding": [
{
@@ -1704,7 +1704,7 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:52:06+00:00"
"time": "2026-01-12T12:37:40+00:00"
},
{
"name": "symfony/options-resolver",
@@ -2358,16 +2358,16 @@
},
{
"name": "symfony/process",
"version": "v8.0.3",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/process.git",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78"
"reference": "10df72602d88c0a3fa685b822976a052611dd607"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/0cbbd88ec836f8757641c651bb995335846abb78",
"reference": "0cbbd88ec836f8757641c651bb995335846abb78",
"url": "https://api.github.com/repos/symfony/process/zipball/10df72602d88c0a3fa685b822976a052611dd607",
"reference": "10df72602d88c0a3fa685b822976a052611dd607",
"shasum": ""
},
"require": {
@@ -2399,7 +2399,7 @@
"description": "Executes commands in sub-processes",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/process/tree/v8.0.3"
"source": "https://github.com/symfony/process/tree/v8.0.4"
},
"funding": [
{
@@ -2419,7 +2419,7 @@
"type": "tidelift"
}
],
"time": "2025-12-19T10:01:18+00:00"
"time": "2026-01-23T11:07:10+00:00"
},
{
"name": "symfony/service-contracts",
@@ -2576,16 +2576,16 @@
},
{
"name": "symfony/string",
"version": "v8.0.1",
"version": "v8.0.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc"
"reference": "758b372d6882506821ed666032e43020c4f57194"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"reference": "ba65a969ac918ce0cc3edfac6cdde847eba231dc",
"url": "https://api.github.com/repos/symfony/string/zipball/758b372d6882506821ed666032e43020c4f57194",
"reference": "758b372d6882506821ed666032e43020c4f57194",
"shasum": ""
},
"require": {
@@ -2642,7 +2642,7 @@
"utf8"
],
"support": {
"source": "https://github.com/symfony/string/tree/v8.0.1"
"source": "https://github.com/symfony/string/tree/v8.0.4"
},
"funding": [
{
@@ -2662,7 +2662,7 @@
"type": "tidelift"
}
],
"time": "2025-12-01T09:13:36+00:00"
"time": "2026-01-12T12:37:40+00:00"
}
],
"packages-dev": [],

View File

@@ -176,8 +176,7 @@ jobs:
run: |
rm -rf vendor composer.lock
composer update --no-scripts --no-plugins -q
mago format --dry-run || true
mago lint --reporting-format=github || true
mago format || true
mago analyze --reporting-format=github || true
sudo chown -R runner:docker resources/lang
.ci/phpcs.sh || true

View File

@@ -158,7 +158,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference'];
}
}

View File

@@ -155,7 +155,10 @@ class TagController extends Controller
'currency_id' => (string) $foreignCurrencyId,
'currency_code' => $journal['foreign_currency_code'],
];
$response[$foreignKey]['difference'] = bcadd((string) $response[$foreignKey]['difference'], Steam::positive($journal['foreign_amount']));
$response[$foreignKey]['difference'] = bcadd(
(string) $response[$foreignKey]['difference'],
Steam::positive($journal['foreign_amount'])
);
$response[$foreignKey]['difference_float'] = (float) $response[$foreignKey]['difference']; // intentional float
}
}

View File

@@ -36,7 +36,7 @@ use League\Fractal\Resource\Item;
/**
* Class UpdateController
*/
class UpdateController extends Controller
final class UpdateController extends Controller
{
private RuleGroupRepositoryInterface $ruleGroupRepository;

View File

@@ -27,7 +27,8 @@ namespace FireflyIII\Api\V1\Controllers\Models\Transaction;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Api\V1\Requests\Models\Transaction\StoreRequest;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
@@ -87,9 +88,9 @@ class StoreController extends Controller
public function store(StoreRequest $request): JsonResponse
{
Log::debug('Now in API StoreController::store()');
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
$data = $request->getAll();
$data['user'] = auth()->user();
$data['user_group'] = $this->userGroup;
Log::channel('audit')->info('Store new transaction over API.', $data);
@@ -109,18 +110,21 @@ class StoreController extends Controller
throw new ValidationException($validator);
}
Preferences::mark();
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
event(new StoredTransactionGroup($transactionGroup, $applyRules, $fireWebhooks));
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $data['apply_rules'] ?? true;
$flags->fireWebhooks = $data['fire_webhooks'] ?? true;
$flags->batchSubmission = $data['batch_submission'] ?? false;
Log::debug('CreatedSingleTransactionGroup');
event(new CreatedSingleTransactionGroup($transactionGroup, $flags));
$manager = $this->getManager();
$manager = $this->getManager();
/** @var User $admin */
$admin = auth()->user();
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
->setUserGroup($this->userGroup)
@@ -130,20 +134,20 @@ class StoreController extends Controller
->withAPIInformation()
;
$selectedGroup = $collector->getGroups()->first();
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw HttpException::fromStatusCode(410, '200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
// enrich
$enrichment = new TransactionGroupEnrichment();
$enrichment = new TransactionGroupEnrichment();
$enrichment->setUser($admin);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
$selectedGroup = $enrichment->enrichSingle($selectedGroup);
/** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class);
$transformer = app(TransactionGroupTransformer::class);
$transformer->setParameters($this->parameters);
$resource = new Item($selectedGroup, $transformer, 'transactions');
$resource = new Item($selectedGroup, $transformer, 'transactions');
return response()->json($manager->createData($resource)->toArray())->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -82,6 +82,9 @@ class UpdateController extends Controller
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
$runRecalculations = $oldHash !== $newHash;
// FIXME responds to a single event.
// flags in array?
event(new UpdatedTransactionGroup($transactionGroup, $applyRules, $fireWebhooks, $runRecalculations));
/** @var User $admin */

View File

@@ -240,8 +240,8 @@ class BasicController extends Controller
'value_parsed' => Amount::formatAnything($currency, $sums[$currencyId]['sum'] ?? '0', false),
'local_icon' => 'balance-scale',
'sub_title' => Amount::formatAnything($currency, $expenses[$currencyId]['sum'] ?? '0', false)
.' + '
.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
.' + '
.Amount::formatAnything($currency, $incomes[$currencyId]['sum'] ?? '0', false),
];
$return[] = [
'key' => sprintf('spent-in-%s', $currency->code),

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V1\Controllers\Webhook;
use FireflyIII\Api\V1\Controllers\Controller;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\Webhook;
@@ -176,8 +176,8 @@ class ShowController extends Controller
}
// trigger event to send them:
Log::debug('send event RequestedSendWebhookMessages from ShowController::triggerTransaction()');
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
return response()->json([], 204);
}

View File

@@ -47,7 +47,7 @@ class StoreRequest extends FormRequest
*/
public function getAll(): array
{
$fields = ['order' => ['order', 'convertInteger']];
$fields = ['order' => ['order', 'convertInteger']];
$data = $this->getAllData($fields);
$data['name'] = $this->convertString('name');
$data['accounts'] = $this->parseAccounts($this->get('accounts'));

View File

@@ -64,6 +64,7 @@ class StoreRequest extends FormRequest
return [
'group_title' => $this->convertString('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'batch_submission' => $this->boolean('batch_submission'),
'apply_rules' => $this->boolean('apply_rules', true),
'fire_webhooks' => $this->boolean('fire_webhooks', true),
'transactions' => $this->getTransactionData(),

View File

@@ -113,7 +113,7 @@ class UpdateRequest extends FormRequest
];
$this->booleanFields = ['reconciled'];
$this->arrayFields = ['tags'];
$data = [];
$data = ['batch_submission' => false];
if ($this->has('transactions')) {
$data['transactions'] = $this->getTransactionData();
}
@@ -123,6 +123,9 @@ class UpdateRequest extends FormRequest
if ($this->has('fire_webhooks')) {
$data['fire_webhooks'] = $this->boolean('fire_webhooks', true);
}
if ($this->has('batch_submission')) {
$data['batch_submission'] = $this->boolean('batch_submission');
}
if ($this->has('group_title')) {
$data['group_title'] = $this->convertString('group_title');
}

View File

@@ -75,8 +75,19 @@ class UpdateRequest extends FormRequest
/** @var Webhook $webhook */
$webhook = $this->route()->parameter('webhook');
return ['title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id), 'active' => [new IsBoolean()],
'trigger' => 'prohibited', 'triggers' => 'required|array|min:1|max:10', 'triggers.*' => sprintf('required|in:%s', $triggers), 'response' => 'prohibited', 'responses' => 'required|array|min:1|max:1', 'responses.*' => sprintf('required|in:%s', $responses), 'delivery' => 'prohibited', 'deliveries' => 'required|array|min:1|max:1', 'deliveries.*' => sprintf('required|in:%s', $deliveries),
'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)]];
return [
'title' => sprintf('min:1|max:255|uniqueObjectForUser:webhooks,title,%d', $webhook->id),
'active' => [new IsBoolean()],
'trigger' => 'prohibited',
'triggers' => 'required|array|min:1|max:10',
'triggers.*' => sprintf('required|in:%s', $triggers),
'response' => 'prohibited',
'responses' => 'required|array|min:1|max:1',
'responses.*' => sprintf('required|in:%s', $responses),
'delivery' => 'prohibited',
'deliveries' => 'required|array|min:1|max:1',
'deliveries.*' => sprintf('required|in:%s', $deliveries),
'url' => [sprintf('url:%s', $validProtocols), sprintf('uniqueExistingWebhook:%d', $webhook->id)],
];
}
}

View File

@@ -57,6 +57,7 @@ class CorrectsGroupAccounts extends Command
foreach ($groups as $groupId) {
$group = TransactionGroup::find($groupId);
// TODO in theory the "unifyAccounts" method could lead to the need for run recalculations.
// FIXME needs to be a collection.
$event = new UpdatedTransactionGroup($group, true, true, false);
$handler->unifyAccounts($event);
}

View File

@@ -68,7 +68,7 @@ class CorrectsMetaDataFields extends Command
private function rename(string $original, string $update): void
{
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$total = DB::table('journal_meta')->where('name', '=', $original)->update(['name' => $update]);
$this->count += $total;
}
}

View File

@@ -295,7 +295,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->sourceTransaction->transaction_currency_id && $this->sourceCurrency instanceof TransactionCurrency) {
$this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->sourceTransaction->id, $this->sourceCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->sourceTransaction->id,
$this->sourceCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->sourceTransaction->save();
@@ -335,7 +339,11 @@ class UpgradesTransferCurrencies extends Command
{
if (null === $this->destinationTransaction->transaction_currency_id && $this->destinationCurrency instanceof TransactionCurrency) {
$this->destinationTransaction->transaction_currency_id = $this->destinationCurrency->id;
$message = sprintf('Transaction #%d has no currency setting, now set to %s.', $this->destinationTransaction->id, $this->destinationCurrency->code);
$message = sprintf(
'Transaction #%d has no currency setting, now set to %s.',
$this->destinationTransaction->id,
$this->destinationCurrency->code
);
$this->friendlyInfo($message);
++$this->count;
$this->destinationTransaction->save();

View File

@@ -0,0 +1,45 @@
<?php
declare(strict_types=1);
/*
* CreatedSingleTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use FireflyIII\Models\TransactionGroup;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Log;
class CreatedSingleTransactionGroup extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public TransactionGroup $transactionGroup,
public TransactionGroupEventFlags $flags
) {
Log::debug(__METHOD__);
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* CreatedTransactionGroupBatch.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
class CreatedTransactionGroupInBatch extends Event
{
use SerializesModels;
/**
* Create a new event instance.
*/
public function __construct(
public Collection $collection,
public array $flags
) {}
}

View File

@@ -0,0 +1,33 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupEventFlags.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
class TransactionGroupEventFlags
{
public bool $applyRules = true;
public bool $fireWebhooks = true;
public bool $batchSubmission = false;
public bool $recalculateCredit = true;
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
/*
* TransactionGroupRequestsAuditLogEntry.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Model\TransactionGroup;
use FireflyIII\Events\Event;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Queue\SerializesModels;
class TransactionGroupRequestsAuditLogEntry extends Event
{
use SerializesModels;
public function __construct(
public Model $changer,
public Model $auditable,
public string $field,
public mixed $before,
public mixed $after
) {}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* RequestedReportOnJournals.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* TransactionGroupsRequestedReporting.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,24 +22,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events\Model\TransactionGroup;
namespace FireflyIII\Events;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Foundation\Events\Dispatchable;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
/**
* Class RequestedReportOnJournals
*/
class RequestedReportOnJournals
class TransactionGroupsRequestedReporting extends Event
{
use Dispatchable;
use InteractsWithSockets;
use SerializesModels;
/**
@@ -46,15 +38,5 @@ class RequestedReportOnJournals
public function __construct(
public int $userId,
public Collection $groups
) {
Log::debug('In event RequestedReportOnJournals.');
}
/**
* Get the channels the event should broadcast on.
*/
public function broadcastOn(): PrivateChannel
{
return new PrivateChannel('channel-name');
}
) {}
}

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* RequestedSendWebhookMessages.php
* Copyright (c) 2021 james@firefly-iii.org
* WebhookMessagesRequestSending.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,16 +22,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Model\Webhook;
use FireflyIII\Events\Event;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedSendWebhookMessages
*/
class RequestedSendWebhookMessages extends Event
class WebhookMessagesRequestSending extends Event
{
use SerializesModels;
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/*
* NewInvitationCreated.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Events\Security\System;
use FireflyIII\Events\Event;
use FireflyIII\Models\InvitedUser;
use Illuminate\Queue\SerializesModels;
class NewInvitationCreated extends Event
{
use SerializesModels;
public function __construct(
public InvitedUser $invitee
) {}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* RequestedVersionCheckStatus.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* SystemRequestedVersionCheck.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,24 +22,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Events;
namespace FireflyIII\Events\Security\System;
use FireflyIII\Events\Event;
use FireflyIII\User;
use Illuminate\Queue\SerializesModels;
/**
* Class RequestedVersionCheckStatus
*/
class RequestedVersionCheckStatus extends Event
class SystemRequestedVersionCheck extends Event
{
use SerializesModels;
/**
* Create a new event instance. This event is triggered when Firefly III wants to know
* what the deal is with the version checker.
*/
public function __construct(
public User $user
) {}

View File

@@ -44,6 +44,6 @@ class UserFailedLoginAttempt extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -44,6 +44,6 @@ class UserHasDisabledMFA extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -44,6 +44,6 @@ class UserHasEnabledMFA extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -46,6 +46,6 @@ class UserHasFewMFABackupCodesLeft extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -44,6 +44,6 @@ class UserHasGeneratedNewBackupCodes extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -44,6 +44,6 @@ class UserHasNoMFABackupCodesLeft extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -44,6 +44,6 @@ class UserHasUsedBackupCode extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -46,6 +46,6 @@ class UserKeepsFailingMFA extends Event
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -40,8 +40,10 @@ class UserSuccessfullyLoggedIn extends Event
{
if ($user instanceof User) {
$this->user = $user;
return;
}
throw new InvalidArgumentException('User must be an instance of User.');
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
}

View File

@@ -50,7 +50,6 @@ use FireflyIII\Services\Internal\Destroy\JournalDestroyService;
use FireflyIII\Services\Internal\Support\JournalServiceTrait;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\User;
use FireflyIII\Validation\AccountValidator;
use Illuminate\Support\Collection;
@@ -110,22 +109,24 @@ class TransactionJournalFactory
{
Log::debug('Now in TransactionJournalFactory::create()');
// convert to special object.
$dataObject = new NullArrayObject($data);
// $dataObject = new NullArrayObject($data);
Log::debug('Start of TransactionJournalFactory::create()');
$collection = new Collection();
$transactions = $dataObject['transactions'] ?? [];
$collection = new Collection();
$transactions = $data['transactions'] ?? [];
if (0 === count($transactions)) {
Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.');
return new Collection();
}
$batchSubmission = $data['batch_submission'] ?? false;
try {
/** @var array $row */
foreach ($transactions as $index => $row) {
$row['batch_submission'] = $batchSubmission;
Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions)));
$journal = $this->createJournal(new NullArrayObject($row));
$journal = $this->createJournal($row);
if ($journal instanceof TransactionJournal) {
$collection->push($journal);
}
@@ -161,7 +162,7 @@ class TransactionJournalFactory
*
* @SuppressWarnings("PHPMD.ExcessiveMethodLength")
*/
private function createJournal(NullArrayObject $row): ?TransactionJournal
private function createJournal(array $row): ?TransactionJournal
{
Log::debug('Now in TransactionJournalFactory::createJournal()');
$row['import_hash_v2'] = $this->hashArray($row);
@@ -273,7 +274,7 @@ class TransactionJournalFactory
'date_tz' => $carbon->format('e'),
'order' => $order,
'tag_count' => 0,
'completed' => 0,
'completed' => !$row['batch_submission'],
]);
Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description));
@@ -331,7 +332,7 @@ class TransactionJournalFactory
throw new FireflyException($e->getMessage(), 0, $e);
}
$journal->completed = true;
Log::debug(sprintf('Is part of a batch submission? %s', var_export($row['batch_submission'], true)));
$journal->save();
$this->storeBudget($journal, $row);
$this->storeCategory($journal, $row);
@@ -344,20 +345,18 @@ class TransactionJournalFactory
return $journal;
}
private function hashArray(NullArrayObject $row): string
private function hashArray(array $row): string
{
$dataRow = $row->getArrayCopy();
unset($dataRow['import_hash_v2'], $dataRow['original_source']);
unset($row['import_hash_v2'], $row['original_source']);
try {
$json = json_encode($dataRow, JSON_THROW_ON_ERROR);
$json = json_encode($row, JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
Log::error(sprintf('Could not encode dataRow: %s', $e->getMessage()));
$json = microtime();
}
$hash = hash('sha256', $json);
Log::debug(sprintf('The hash is: %s', $hash), $dataRow);
$hash = hash('sha256', $json);
Log::debug(sprintf('The hash is: %s', $hash), $row);
return $hash;
}
@@ -399,7 +398,7 @@ class TransactionJournalFactory
/**
* @throws FireflyException
*/
private function validateAccounts(NullArrayObject $data): void
private function validateAccounts(array $data): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
$transactionType = $data['type'] ?? 'invalid';
@@ -577,7 +576,7 @@ class TransactionJournalFactory
/**
* Link a piggy bank to this journal.
*/
private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void
private function storePiggyEvent(TransactionJournal $journal, array $data): void
{
Log::debug('Will now store piggy event.');
@@ -592,17 +591,17 @@ class TransactionJournalFactory
Log::debug('Create no piggy event');
}
private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void
private function storeMetaFields(TransactionJournal $journal, array $transaction): void
{
foreach ($this->fields as $field) {
$this->storeMeta($journal, $transaction, $field);
}
}
protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void
protected function storeMeta(TransactionJournal $journal, array $data, string $field): void
{
$set = ['journal' => $journal, 'name' => $field, 'data' => (string) ($data[$field] ?? '')];
if ($data[$field] instanceof Carbon) {
if (array_key_exists($field, $data) && $data[$field] instanceof Carbon) {
$data[$field]->setTimezone(config('app.timezone'));
Log::debug(sprintf('%s Date: %s (%s)', $field, $data[$field], $data[$field]->timezone->getName()));
$set['data'] = $data[$field]->format('Y-m-d H:i:s');
@@ -615,7 +614,7 @@ class TransactionJournalFactory
$factory->updateOrCreate($set);
}
private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void
private function storeLocation(TransactionJournal $journal, array $data): void
{
if (!in_array(null, [$data['longitude'], $data['latitude'], $data['zoom_level']], true)) {
$location = new Location();

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\DestroyedTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
@@ -56,8 +56,8 @@ class DestroyedGroupEventHandler
$engine->setObjects(new Collection()->push($group));
$engine->setTrigger(WebhookTrigger::DESTROY_TRANSACTION);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function updateRunningBalance(DestroyedTransactionGroup $event): void

View File

@@ -25,7 +25,7 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\TriggeredStoredTransactionGroup;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\RuleGroup;
@@ -47,16 +47,16 @@ class StoredGroupEventHandler
{
public function runAllHandlers(StoredTransactionGroup $event): void
{
$this->processRules($event, null);
$this->recalculateCredit($event);
$this->triggerWebhooks($event);
// $this->processRules($event, null);
// $this->recalculateCredit($event);
// $this->triggerWebhooks($event);
$this->removePeriodStatistics($event);
}
public function triggerRulesManually(TriggeredStoredTransactionGroup $event): void
{
$newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
$this->processRules($newEvent, $event->ruleGroup);
// $newEvent = new StoredTransactionGroup($event->transactionGroup, true, false);
// $this->processRules($newEvent, $event->ruleGroup);
}
/**
@@ -181,7 +181,7 @@ class StoredGroupEventHandler
$engine->generateMessages();
// trigger event to send them:
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}

View File

@@ -25,7 +25,7 @@ namespace FireflyIII\Handlers\Events;
use FireflyIII\Enums\TransactionTypeEnum;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Events\UpdatedTransactionGroup;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
@@ -207,8 +207,8 @@ class UpdatedGroupEventHandler
$engine->setTrigger(WebhookTrigger::UPDATE_TRANSACTION);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function updateRunningBalance(UpdatedTransactionGroup $event): void

View File

@@ -25,7 +25,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Support\Facades\Amount;
@@ -58,8 +58,8 @@ class BudgetLimitObserver
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
@@ -101,8 +101,8 @@ class BudgetLimitObserver
$engine->setTrigger(WebhookTrigger::STORE_UPDATE_BUDGET_LIMIT);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
}

View File

@@ -24,7 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\Budget;
@@ -59,8 +59,8 @@ class BudgetObserver
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::STORE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
@@ -80,8 +80,8 @@ class BudgetObserver
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::UPDATE_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
@@ -97,8 +97,8 @@ class BudgetObserver
$engine->setObjects(new Collection()->push($budget));
$engine->setTrigger(WebhookTrigger::DESTROY_BUDGET);
$engine->generateMessages();
Log::debug(sprintf('send event RequestedSendWebhookMessages from %s', __METHOD__));
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
$repository = app(AttachmentRepositoryInterface::class);
$repository->setUser($budget->user);

View File

@@ -24,7 +24,6 @@ declare(strict_types=1);
namespace FireflyIII\Handlers\Observer;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -40,19 +39,8 @@ class TransactionObserver
public function created(Transaction $transaction): void
{
Log::debug('Observe "created" of a transaction.');
if (
true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data && (
1 === bccomp($transaction->amount, '0')
&& self::$recalculate
)
) {
Log::debug('Trigger recalculateForJournal');
$journal = $transaction->transactionJournal;
if ($journal instanceof TransactionJournal) {
AccountBalanceCalculator::recalculateForJournal($journal);
}
}
return;
$this->updatePrimaryCurrencyAmount($transaction);
}

View File

@@ -773,30 +773,22 @@ trait MetaCollection
$this->query->whereNotNull('tag_transaction_journal.tag_id');
// Added this while fixing #10898, not sure why a post filter was ever necessary.
$this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray());
// Removed again for #11473 because it breaks multiple tag filters.
// $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray());
// this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray();
$list = array_map(strtolower(...), $list);
$filter = static function (array $object) use ($list): bool {
Log::debug(sprintf('Now in setTags(%s) filter', implode(', ', $list)));
Log::debug(sprintf('Now in setTags (any) filter: %s', implode(', ', $list)));
foreach ($object['transactions'] as $transaction) {
$total = count($transaction['tags']);
$matched = 0;
foreach ($transaction['tags'] as $tag) {
Log::debug(sprintf('"%s" versus', strtolower((string) $tag['name'])), $list);
if (in_array(strtolower((string) $tag['name']), $list, true)) {
Log::debug(sprintf('Transaction has tag "%s" so return true.', $tag['name']));
++$matched;
if (1 === count($list)) {
return true;
}
}
}
if (count($list) > 1 && $total === $matched && $matched === count($list)) {
Log::debug(sprintf('All %d searched tags are present.', $total));
return true;
return true;
}
}
}
Log::debug('Transaction has no tags from the list, so return false.');

View File

@@ -433,6 +433,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function getGroups(): Collection
{
Log::debug('Now in getGroups()');
if ($this->expandGroupSearch) {
// get group ID's for the query:
$groupIds = $this->getCollectedGroupIds();
@@ -440,6 +441,8 @@ class GroupCollector implements GroupCollectorInterface
$this->query->orWhereIn('transaction_journals.transaction_group_id', $groupIds);
}
$result = $this->query->get($this->fields);
// $this->dumpQueryInLogs();
// Log::debug(sprintf('Count of result is %d', $result->count()));
// now to parse this into an array.
$collection = $this->parseArray($result);
@@ -758,6 +761,12 @@ class GroupCollector implements GroupCollectorInterface
count($currentCollection)
));
if (0 === $currentCollection->count()) {
Log::debug('Found nothing anyway, return empty collection.');
return $currentCollection;
}
/**
* @var Closure $function
*/
@@ -775,16 +784,25 @@ class GroupCollector implements GroupCollectorInterface
$result = $function($item);
if (false === $result) {
// skip other filters, continue to next item.
Log::debug('Result is false');
continue;
}
// if the result is a bool, use the unedited results.
if (true === $result) {
Log::debug('Result is true');
$nextCollection->push($item);
continue;
}
// if the result is an array, the filter has changed what's being returned.
if (is_array($result)) {
Log::debug('Result is array');
$nextCollection->push($result);
continue;
}
Log::debug('Result is something else!');
}
$currentCollection = $nextCollection;
Log::debug(sprintf('GroupCollector: postFilterCollection has %d transaction(s) left.', count($currentCollection)));
@@ -827,6 +845,7 @@ class GroupCollector implements GroupCollectorInterface
*/
public function getPaginatedGroups(): LengthAwarePaginator
{
Log::debug('Now in getPaginatedGroups()');
$set = $this->getGroups();
if (0 === $this->limit) {
$this->setLimit(50);

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Admin;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Middleware\IsDemoUser;
@@ -202,7 +202,7 @@ class UserController extends Controller
session()->flash('info', trans('firefly.user_is_invited', ['address' => $address]));
// event!
event(new InvitationCreated($invitee));
event(new NewInvitationCreated($invitee));
return redirect(route('settings.users'));
}

View File

@@ -99,10 +99,7 @@ class LoginController extends Controller
// basic validation exception.
// report the failed login to the user if the count is 2 or 5.
// TODO here be warning.
return redirect(route('login'))
->withErrors([$this->username => trans('auth.failed')])
->onlyInput($this->username)
;
return redirect(route('login'))->withErrors([$this->username => trans('auth.failed')])->onlyInput($this->username);
}
Log::debug('Login data is present.');
@@ -170,7 +167,7 @@ class LoginController extends Controller
*/
protected function sendFailedLoginResponse(Request $request): void
{
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception = ValidationException::withMessages([$this->username() => [trans('auth.failed')]]);
$exception->redirectTo = route('login');
throw $exception;

View File

@@ -192,7 +192,10 @@ class IndexController extends Controller
if (count($bill['paid_dates']) < count($bill['pay_dates'])) {
$count = count($bill['pay_dates']) - count($bill['paid_dates']);
if ($count > 0) {
$avg = bcdiv(bcadd((string) $bill['amount_min'], (string) $bill['amount_max']), '2');
$avg = bcdiv(
bcadd((string) $bill['amount_min'], (string) $bill['amount_max']),
'2'
);
$avg = bcmul($avg, (string) $count);
$sums[$groupOrder][$currencyId]['total_left_to_pay'] = bcadd($sums[$groupOrder][$currencyId]['total_left_to_pay'], $avg);
Log::debug(

View File

@@ -197,7 +197,13 @@ class BudgetLimitController extends Controller
if ($request->expectsJson()) {
$array = $limit->toArray();
// add some extra metadata:
$spentArr = $this->opsRepository->sumExpenses($limit->start_date, $limit->end_date, null, new Collection()->push($budget), $currency);
$spentArr = $this->opsRepository->sumExpenses(
$limit->start_date,
$limit->end_date,
null,
new Collection()->push($budget),
$currency
);
$array['spent'] = $spentArr[$currency->id]['sum'] ?? '0';
$array['left_formatted'] = Amount::formatAnything($limit->transactionCurrency, bcadd($array['spent'], (string) $array['amount']));
$array['amount_formatted'] = Amount::formatAnything($limit->transactionCurrency, $limit['amount']);

View File

@@ -51,7 +51,7 @@ use Illuminate\View\View;
/**
* Class IndexController
*/
class IndexController extends Controller
final class IndexController extends Controller
{
use DateCalculation;

View File

@@ -73,7 +73,7 @@ class BillController extends Controller
*/
foreach ($paid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.paid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}
@@ -82,7 +82,7 @@ class BillController extends Controller
*/
foreach ($unpaid as $info) {
$amount = $info['sum'];
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$label = (string) trans('firefly.unpaid_in_currency', ['currency' => $info['name']]);
$chartData[$label] = ['amount' => $amount, 'currency_symbol' => $info['symbol'], 'currency_code' => $info['code']];
}

View File

@@ -539,7 +539,13 @@ class BudgetController extends Controller
}
// get spent amount in this period for this currency.
$sum = $this->opsRepository->sumExpenses($currentStart, $currentEnd, $accounts, new Collection()->push($budget), $currency);
$sum = $this->opsRepository->sumExpenses(
$currentStart,
$currentEnd,
$accounts,
new Collection()->push($budget),
$currency
);
$amount = Steam::positive($sum[$currency->id]['sum'] ?? '0');
$chartData[0]['entries'][$title] = Steam::bcround($amount, $currency->decimal_places);

View File

@@ -76,7 +76,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$budget = $journal['budget_name'] ?? (string) trans('firefly.no_budget');
$title = sprintf('%s (%s)', $budget, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -122,7 +126,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$category = $journal['category_name'] ?? (string) trans('firefly.no_category');
$title = sprintf('%s (%s)', $category, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -168,7 +176,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['destination_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);
@@ -214,7 +226,11 @@ class TransactionController extends Controller
foreach ($result as $journal) {
$name = $journal['source_account_name'];
$title = sprintf('%s (%s)', $name, $journal['currency_symbol']);
$data[$title] ??= ['amount' => '0', 'currency_symbol' => $journal['currency_symbol'], 'currency_code' => $journal['currency_code']];
$data[$title] ??= [
'amount' => '0',
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
];
$data[$title]['amount'] = bcadd($data[$title]['amount'], (string) $journal['amount']);
}
$chart = $this->generator->multiCurrencyPieChart($data);

View File

@@ -23,7 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Events\RequestedSendWebhookMessages;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Facades\Amount;
use FireflyIII\Support\Facades\FireflyConfig;
@@ -152,10 +152,10 @@ abstract class Controller extends BaseController
View::share('original_route_name', Route::currentRouteName());
// lottery to send any remaining webhooks:
if (7 === random_int(1, 10)) {
if (7 === random_int(1, 30)) {
// trigger event to send them:
Log::debug('send event RequestedSendWebhookMessages through lottery');
event(new RequestedSendWebhookMessages());
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
}
View::share('darkMode', $darkMode);

View File

@@ -27,7 +27,7 @@ use Carbon\Carbon;
use Carbon\Exceptions\InvalidFormatException;
use Exception;
use FireflyIII\Enums\AccountTypeEnum;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Events\Security\System\SystemRequestedVersionCheck;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Http\Middleware\Installer;
@@ -178,7 +178,7 @@ class HomeController extends Controller
/** @var User $user */
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
event(new SystemRequestedVersionCheck($user));
return view('index', [
'count' => $count,
@@ -202,7 +202,7 @@ class HomeController extends Controller
/** @var User $user */
$user = auth()->user();
event(new RequestedVersionCheckStatus($user));
event(new SystemRequestedVersionCheck($user));
return view('index', ['subTitle' => $subTitle, 'start' => $start, 'end' => $end, 'pageTitle' => $pageTitle]);
}

View File

@@ -95,7 +95,11 @@ class CategoryController extends Controller
'categories' => [],
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'] = bcadd(
$report[$sourceAccountId]['currencies'][$currencyId]['categories'][$category['id']]['spent'],
(string) $journal['amount']
@@ -122,7 +126,11 @@ class CategoryController extends Controller
'currency_decimal_places' => $currency['currency_decimal_places'],
'categories' => [],
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= ['spent' => '0', 'earned' => '0', 'sum' => '0'];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']] ??= [
'spent' => '0',
'earned' => '0',
'sum' => '0',
];
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'] = bcadd(
$report[$destinationId]['currencies'][$currencyId]['categories'][$category['id']]['earned'],
(string) $journal['amount']

View File

@@ -24,7 +24,8 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Transaction;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
@@ -76,7 +77,9 @@ class CreateController extends Controller
$newGroup = $service->cloneGroup($group);
// event!
event(new StoredTransactionGroup($newGroup, true, true));
$flags = new TransactionGroupEventFlags();
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($newGroup, true, true));
Preferences::mark();
@@ -107,29 +110,29 @@ class CreateController extends Controller
{
Preferences::mark();
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
$sourceId = (int) request()->get('source');
$destinationId = (int) request()->get('destination');
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
$accountRepository = app(AccountRepositoryInterface::class);
$cash = $accountRepository->getCashAccount();
$preFilled = session()->has('preFilled') ? session('preFilled') : [];
$subTitle = (string) trans(sprintf('breadcrumbs.create_%s', strtolower((string) $objectType)));
$subTitleIcon = 'fa-plus';
/** @var null|array $optionalFields */
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$previousUrl = $this->rememberPreviousUrl('transactions.create.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -139,13 +142,13 @@ class CreateController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
session()->put('preFilled', $preFilled);

View File

@@ -83,29 +83,29 @@ class EditController extends Controller
}
/** @var AccountRepositoryInterface $repository */
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
$repository = app(AccountRepositoryInterface::class);
$allowedOpposingTypes = config('firefly.allowed_opposing_types');
$accountToTypes = config('firefly.account_to_transaction');
$expectedSourceTypes = config('firefly.expected_source_types');
$allowedSourceDests = config('firefly.source_dests');
$title = $transactionGroup->transactionJournals()->count() > 1
? $transactionGroup->title
: $transactionGroup->transactionJournals()->first()->description;
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
$subTitle = (string) trans('firefly.edit_transaction_title', ['description' => $title]);
$subTitleIcon = 'fa-plus';
$cash = $repository->getCashAccount();
$previousUrl = $this->rememberPreviousUrl('transactions.edit.url');
$parts = parse_url((string) $previousUrl);
$search = sprintf('?%s', $parts['query'] ?? '');
$previousUrl = str_replace($search, '', $previousUrl);
// settings necessary for v2
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
if (!is_array($optionalFields)) {
$optionalFields = [];
}
// not really a fan of this, but meh.
$optionalDateFields = [
$optionalDateFields = [
'interest_date' => $optionalFields['interest_date'] ?? false,
'book_date' => $optionalFields['book_date'] ?? false,
'process_date' => $optionalFields['process_date'] ?? false,
@@ -115,13 +115,13 @@ class EditController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location']
= $optionalFields['location'] && true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
$optionalFields['location'] = $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info voor v2:
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
$longitude = config('firefly.default_location.longitude');
$latitude = config('firefly.default_location.latitude');
$zoomLevel = config('firefly.default_location.zoom_level');
return view('transactions.edit', [
'cash' => $cash,

View File

@@ -78,7 +78,7 @@ class EditController extends Controller
}
$subTitleIcon = 'fa-pencil';
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$subTitle = (string) trans('breadcrumbs.edit_currency', ['name' => $currency->name]);
$currency->symbol = htmlentities($currency->symbol);
// is currently enabled (for this user?)

View File

@@ -23,34 +23,21 @@ declare(strict_types=1);
namespace FireflyIII\Http;
use FireflyIII\Http\Middleware\AcceptHeaders;
use FireflyIII\Http\Middleware\Authenticate;
use FireflyIII\Http\Middleware\Binder;
use FireflyIII\Http\Middleware\EncryptCookies;
use FireflyIII\Http\Middleware\InstallationId;
use FireflyIII\Http\Middleware\Installer;
use FireflyIII\Http\Middleware\InterestingMessage;
use FireflyIII\Http\Middleware\IsAdmin;
use FireflyIII\Http\Middleware\Range;
use FireflyIII\Http\Middleware\RedirectIfAuthenticated;
use FireflyIII\Http\Middleware\SecureHeaders;
use FireflyIII\Http\Middleware\StartFireflySession;
use FireflyIII\Http\Middleware\TrimStrings;
use FireflyIII\Http\Middleware\TrustProxies;
use FireflyIII\Http\Middleware\VerifyCsrfToken;
use Illuminate\Auth\Middleware\AuthenticateWithBasicAuth;
use Illuminate\Auth\Middleware\Authorize;
use Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode;
use Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull;
use Illuminate\Foundation\Http\Middleware\ValidatePostSize;
use Illuminate\Routing\Middleware\ThrottleRequests;
use Illuminate\Session\Middleware\AuthenticateSession;
use Illuminate\View\Middleware\ShareErrorsFromSession;
use Laravel\Passport\Http\Middleware\CreateFreshApiToken;
use Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful;
use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware;
/**
* Class Kernel
@@ -58,7 +45,7 @@ use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware;
class Kernel extends HttpKernel
{
protected $middleware = [
SecureHeaders::class,
// SecureHeaders::class,
CheckForMaintenanceMode::class,
ValidatePostSize::class,
TrimStrings::class,
@@ -74,102 +61,5 @@ class Kernel extends HttpKernel
'guest' => RedirectIfAuthenticated::class,
'throttle' => ThrottleRequests::class,
];
protected $middlewareGroups = [
// does not check login
// does not check 2fa
// does not check activation
'web' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
AuthenticateSession::class,
CreateFreshApiToken::class,
],
// only the basic variable binders.
'binders-only' => [Installer::class, EncryptCookies::class, AddQueuedCookiesToResponse::class, Binder::class],
// MUST NOT be logged in. Does not care about 2FA or confirmation.
'user-not-logged-in' => [
Installer::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
RedirectIfAuthenticated::class,
],
// MUST be logged in.
// MUST NOT have 2FA
// don't care about confirmation:
'user-logged-in-no-2fa' => [
Installer::class,
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
Authenticate::class,
// RedirectIfTwoFactorAuthenticated::class,
],
// MUST be logged in
// don't care about 2fa
// don't care about confirmation.
'user-simple-auth' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Binder::class,
Authenticate::class,
],
// MUST be logged in
// MUST have 2fa
// MUST be confirmed.
// (this group includes the other Firefly III middleware)
'user-full-auth' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Authenticate::class,
MFAMiddleware::class,
Range::class,
Binder::class,
InterestingMessage::class,
CreateFreshApiToken::class,
],
// MUST be logged in
// MUST have 2fa
// MUST be confirmed.
// MUST have owner role
// (this group includes the other Firefly III middleware)
'admin' => [
EncryptCookies::class,
AddQueuedCookiesToResponse::class,
StartFireflySession::class,
ShareErrorsFromSession::class,
VerifyCsrfToken::class,
Authenticate::class,
// AuthenticateTwoFactor::class,
IsAdmin::class,
Range::class,
Binder::class,
CreateFreshApiToken::class,
],
// full API authentication
'api' => [AcceptHeaders::class, EnsureFrontendRequestsAreStateful::class, 'auth:api,sanctum', 'bindings'],
// do only bindings, no auth
'api_basic' => [AcceptHeaders::class, 'bindings'],
];
protected $middlewarePriority = [StartFireflySession::class, ShareErrorsFromSession::class, Authenticate::class, Binder::class, Authorize::class];
}

View File

@@ -75,7 +75,7 @@ class Binder
*
* @return mixed
*/
private function performBinding(string $key, string $value, Route $route)
private function performBinding(string $key, object|string $value, Route $route)
{
$class = $this->binders[$key];

View File

@@ -40,12 +40,16 @@ class ConfigurationRequest extends FormRequest
*/
public function getConfigurationData(): array
{
return ['single_user_mode' => $this->boolean('single_user_mode'),
'enable_exchange_rates' => $this->boolean('enable_exchange_rates'), 'use_running_balance' => $this->boolean('use_running_balance'),
'enable_external_map' => $this->boolean(
'enable_external_map'
), 'enable_external_rates' => $this->boolean('enable_external_rates'), 'allow_webhooks' => $this->boolean('allow_webhooks'),
'valid_url_protocols' => $this->string('valid_url_protocols'), 'is_demo_site' => $this->boolean('is_demo_site')];
return [
'single_user_mode' => $this->boolean('single_user_mode'),
'enable_exchange_rates' => $this->boolean('enable_exchange_rates'),
'use_running_balance' => $this->boolean('use_running_balance'),
'enable_external_map' => $this->boolean('enable_external_map'),
'enable_external_rates' => $this->boolean('enable_external_rates'),
'allow_webhooks' => $this->boolean('allow_webhooks'),
'valid_url_protocols' => $this->string('valid_url_protocols'),
'is_demo_site' => $this->boolean('is_demo_site'),
];
}
/**
@@ -54,10 +58,16 @@ class ConfigurationRequest extends FormRequest
public function rules(): array
{
// fixed
return ['single_user_mode' => 'min:0|max:1|numeric',
'enable_exchange_rates' => 'min:0|max:1|numeric', 'use_running_balance' => 'min:0|max:1|numeric',
'enable_external_map' => 'min:0|max:1|numeric', 'enable_external_rates' => 'min:0|max:1|numeric', 'allow_webhooks' => 'min:0|max:1|numeric',
'valid_url_protocols' => 'min:0|max:255', 'is_demo_site' => 'min:0|max:1|numeric'];
return [
'single_user_mode' => 'min:0|max:1|numeric',
'enable_exchange_rates' => 'min:0|max:1|numeric',
'use_running_balance' => 'min:0|max:1|numeric',
'enable_external_map' => 'min:0|max:1|numeric',
'enable_external_rates' => 'min:0|max:1|numeric',
'allow_webhooks' => 'min:0|max:1|numeric',
'valid_url_protocols' => 'min:0|max:255',
'is_demo_site' => 'min:0|max:1|numeric',
];
}
public function withValidator(Validator $validator): void

View File

@@ -25,8 +25,9 @@ declare(strict_types=1);
namespace FireflyIII\Jobs;
use Carbon\Carbon;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupEventFlags;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupsRequestedReporting;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Recurrence;
@@ -145,7 +146,7 @@ class CreateRecurringTransactions implements ShouldQueue
Log::debug('Now running report thing.');
// will now send email to users.
foreach ($result as $userId => $journals) {
event(new RequestedReportOnJournals($userId, $journals));
event(new TransactionGroupsRequestedReporting($userId, $journals));
}
Log::debug('Done with handle()');
@@ -383,7 +384,10 @@ class CreateRecurringTransactions implements ShouldQueue
Log::info(sprintf('Created new transaction group #%d', $group->id));
// trigger event:
event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$flags = new TransactionGroupEventFlags();
$flags->applyRules = $recurrence->apply_rules;
event(new CreatedSingleTransactionGroup($group, $flags));
// event(new StoredTransactionGroup($group, $recurrence->apply_rules, true));
$this->groups->push($group);
// update recurring thing:

View File

@@ -1,8 +1,10 @@
<?php
/**
* AutomationHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* MailsNewTransactionsReport.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,34 +22,24 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Model\TransactionGroup;
use Exception;
use FireflyIII\Events\RequestedReportOnJournals;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupsRequestedReporting;
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\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
/**
* Class AutomationHandler
*/
class AutomationHandler
class MailsNewTransactionsReport implements ShouldQueue
{
/**
* Respond to the creation of X journals.
*
* @throws FireflyException
*/
public function reportJournals(RequestedReportOnJournals $event): void
public function handle(TransactionGroupsRequestedReporting $event): void
{
Log::debug('In reportJournals.');
Log::debug('In MailsNewTransactionsReport.');
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
/*
* ProcessesNewTransactionGroup.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
namespace FireflyIII\Listeners\Model\TransactionGroup;
use Carbon\Carbon;
use FireflyIII\Enums\WebhookTrigger;
use FireflyIII\Events\Model\TransactionGroup\CreatedSingleTransactionGroup;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Generator\Webhook\MessageGeneratorInterface;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournalMeta;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\PeriodStatistic\PeriodStatisticRepositoryInterface;
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
use FireflyIII\Services\Internal\Support\CreditRecalculateService;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\Support\Models\AccountBalanceCalculator;
use FireflyIII\TransactionRules\Engine\RuleEngineInterface;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
class ProcessesNewTransactionGroup implements ShouldQueue
{
public function handle(CreatedSingleTransactionGroup $event): void
{
Log::debug(sprintf('In ProcessesNewTransactionGroup::handle(#%d)', $event->transactionGroup->id));
if (true === $event->flags->batchSubmission) {
Log::debug(sprintf('Will do nothing for group #%d because it is part of a batch.', $event->transactionGroup->id));
return;
}
Log::debug(sprintf('Will join group #%d with all other open transaction groups and process them.', $event->transactionGroup->id));
$collection = $event->transactionGroup->transactionJournals;
$repository = app(JournalRepositoryInterface::class);
$set = $collection->merge($repository->getUncompletedJournals());
if (0 === $set->count()) {
Log::debug('Set is empty, never mind.');
return;
}
if (!$event->flags->applyRules) {
Log::debug(sprintf('Will NOT process rules for %d journal(s)', $set->count()));
}
if (!$event->flags->recalculateCredit) {
Log::debug(sprintf('Will NOT recalculate credit for %d journal(s)', $set->count()));
}
if (!$event->flags->fireWebhooks) {
Log::debug(sprintf('Will NOT fire webhooks for %d journal(s)', $set->count()));
}
if ($event->flags->applyRules) {
$this->processRules($set);
}
if ($event->flags->recalculateCredit) {
$this->recalculateCredit($set);
}
if ($event->flags->fireWebhooks) {
$this->fireWebhooks($set);
}
// always remove old statistics.
$this->removePeriodStatistics($set);
// recalculate running balance if necessary.
Log::debug('Observe "created" of a transaction.');
if (true === FireflyConfig::get('use_running_balance', config('firefly.feature_flags.running_balance_column'))->data) {
$this->recalculateRunningBalance($set);
}
$repository->markAsCompleted($set);
}
private function recalculateRunningBalance(Collection $set): void
{
Log::debug('Now in recalculateRunningBalance');
// find the earliest date in the set, based on date and _internal_previous_date
$earliest = $set->pluck('date')->sort()->first();
$entries = TransactionJournalMeta::whereIn('transaction_journal_id', $set->pluck('id')->toArray())->where('name', '_internal_previous_date')->get([
'journal_meta.*',
]);
$array = $entries->toArray();
if (count($array) > 0) {
usort($array, function (array $a, array $b) {
return Carbon::parse($a['data'])->gt(Carbon::parse($b['data']));
});
/** @var Carbon $date */
$date = Carbon::parse($array[0]['data']);
$earliest = $date->lt($earliest) ? $date : $earliest;
}
// get accounts
$accounts = Account::leftJoin('transactions', 'transactions.account_id', 'accounts.id')
->leftJoin('transaction_journals', 'transaction_journals.id', 'transactions.transaction_journal_id')
->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id')
->whereIn('transaction_journals.id', $set->pluck('id')->toArray())
->get(['accounts.*'])
;
AccountBalanceCalculator::optimizedCalculation($accounts, $earliest);
}
private function removePeriodStatistics(Collection $set): void
{
Log::debug('Always remove period statistics');
/** @var PeriodStatisticRepositoryInterface $repository */
$repository = app(PeriodStatisticRepositoryInterface::class);
$repository->deleteStatisticsForCollection($set);
}
private function fireWebhooks(Collection $set): void
{
// collect transaction groups by set ids.
$groups = TransactionGroup::whereIn('id', array_unique($set->pluck('transaction_group_id')->toArray()))->get();
Log::debug(__METHOD__);
$user = $set->first()->user;
/** @var MessageGeneratorInterface $engine */
$engine = app(MessageGeneratorInterface::class);
$engine->setUser($user);
// tell the generator which trigger it should look for
$engine->setTrigger(WebhookTrigger::STORE_TRANSACTION);
// tell the generator which objects to process
$engine->setObjects($groups);
// tell the generator to generate the messages
$engine->generateMessages();
// trigger event to send them:
Log::debug(sprintf('send event WebhookMessagesRequestSending from %s', __METHOD__));
event(new WebhookMessagesRequestSending());
}
private function recalculateCredit(Collection $set): void
{
Log::debug(sprintf('Will now recalculateCredit for %d journal(s)', $set->count()));
/** @var CreditRecalculateService $object */
$object = app(CreditRecalculateService::class);
$object->setJournals($set);
$object->recalculate();
}
private function processRules(Collection $set): void
{
Log::debug(sprintf('Will now processRules for %d journal(s)', $set->count()));
$array = $set->pluck('id')->toArray();
$journalIds = implode(',', $array);
$user = $set->first()->user;
Log::debug(sprintf('Add local operator for journal(s): %s', $journalIds));
// collect rules:
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
$ruleGroupRepository->setUser($user);
// add the groups to the rule engine.
// it should run the rules in the group and cancel the group if necessary.
Log::debug('Fire processRules with ALL store-journal rule groups.');
$groups = $ruleGroupRepository->getRuleGroupsWithRules('store-journal');
// create and fire rule engine.
$newRuleEngine = app(RuleEngineInterface::class);
$newRuleEngine->setUser($user);
$newRuleEngine->addOperator(['type' => 'journal_id', 'value' => $journalIds]);
$newRuleEngine->setRuleGroups($groups);
$newRuleEngine->fire();
}
}

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* AuditEventHandler.php
* Copyright (c) 2022 james@firefly-iii.org
* StoresAuditLogEntry.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,21 +22,17 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Model\TransactionGroup;
use Carbon\Carbon;
use FireflyIII\Events\TriggeredAuditLog;
use FireflyIII\Events\Model\TransactionGroup\TransactionGroupRequestsAuditLogEntry;
use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
/**
* Class AuditEventHandler
*/
class AuditEventHandler
class StoresAuditLogEntry implements ShouldQueue
{
public function storeAuditEvent(TriggeredAuditLog $event): void
public function handle(TransactionGroupRequestsAuditLogEntry $event): void
{
$array = [
'auditable' => $event->auditable,

View File

@@ -1,8 +1,10 @@
<?php
declare(strict_types=1);
/*
* WebhookEventHandler.php
* Copyright (c) 2021 james@firefly-iii.org
* SendsWebhookMessages.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -20,26 +22,20 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Model\Webhook;
use FireflyIII\Events\Model\Webhook\WebhookMessagesRequestSending;
use FireflyIII\Jobs\SendWebhookMessage;
use FireflyIII\Models\WebhookMessage;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
/**
* Class WebhookEventHandler
*/
class WebhookEventHandler
class SendsWebhookMessages implements ShouldQueue
{
/**
* Will try to send at most 3 messages so the flow doesn't get broken too much.
*/
public function sendWebhookMessages(): void
public function handle(WebhookMessagesRequestSending $event): void
{
Log::debug(sprintf('Now in %s', __METHOD__));
Log::debug(sprintf('Now in %s for %s', __METHOD__, get_class($event)));
if (false === config('firefly.feature_flags.webhooks') || false === FireflyConfig::get('allow_webhooks', config('firefly.allow_webhooks'))->data) {
Log::debug('Webhook event handler is disabled, do not run sendWebhookMessages().');
@@ -69,6 +65,6 @@ class WebhookEventHandler
}
// clean up sent messages table:
WebhookMessage::where('webhook_messages.sent', true)->where('webhook_messages.created_at', '<', now()->subDays(30))->delete();
WebhookMessage::where('webhook_messages.sent', true)->where('webhook_messages.created_at', '<', now()->subDays(14))->delete();
}
}

View File

@@ -1,8 +1,10 @@
<?php
/**
* VersionCheckEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* ChecksForNewVersion.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,40 +21,28 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Security\System;
use Carbon\Carbon;
use Deprecated;
use FireflyIII\Events\RequestedVersionCheckStatus;
use FireflyIII\Events\Security\System\SystemRequestedVersionCheck;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Update\UpdateTrait;
use FireflyIII\Models\Configuration;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Class VersionCheckEventHandler
*/
class VersionCheckEventHandler
class ChecksForNewVersion implements ShouldQueue
{
use UpdateTrait;
/**
* Checks with GitHub to see if there is a new version.
*
* @throws ContainerExceptionInterface
* @throws FireflyException
* @throws NotFoundExceptionInterface
*/
#[Deprecated(message: '?')]
public function checkForUpdates(RequestedVersionCheckStatus $event): void
public function handle(SystemRequestedVersionCheck $event): void
{
Log::debug('Now in checkForUpdates()');
Log::debug(sprintf('Now in %s', __METHOD__));
// should not check for updates:
$permission = FireflyConfig::get('permission_update_check', -1);
@@ -96,7 +86,7 @@ class VersionCheckEventHandler
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void
private function warnToCheckForUpdates(SystemRequestedVersionCheck $event): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);

View File

@@ -38,10 +38,11 @@ use FireflyIII\Notifications\User\UserRegistration as UserRegistrationNotificati
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\FireflyConfig;
use FireflyIII\User;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class HandlesNewUserRegistration
class HandlesNewUserRegistration implements ShouldQueue
{
public function handle(NewUserRegistered $event): void
{

View File

@@ -1,8 +1,10 @@
<?php
/**
* AdminEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* NotifiesAboutNewInvitation.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,24 +21,47 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Security\System;
use Exception;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Events\Security\System\NewInvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\InvitationMail;
use FireflyIII\Models\InvitedUser;
use FireflyIII\Notifications\Admin\UserInvitation;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Notification;
/**
* Class AdminEventHandler.
*/
class AdminEventHandler
class NotifiesAboutNewInvitation implements ShouldQueue
{
public function sendInvitationNotification(InvitationCreated $event): void
public function handle(NewInvitationCreated $event): void
{
$this->sendInvitationNotification($event->invitee);
$this->sendRegistrationInvite($event->invitee);
}
private function sendRegistrationInvite(InvitedUser $invitee): void
{
$email = $invitee->email;
$admin = $invitee->user->email;
$url = route('invite', [$invitee->invite_code]);
try {
Mail::to($email)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e);
}
}
private function sendInvitationNotification(InvitedUser $invitee): void
{
$sendMail = FireflyConfig::get('notification_invite_created', true)->data;
if (false === $sendMail) {
@@ -44,7 +69,7 @@ class AdminEventHandler
}
try {
Notification::send(new OwnerNotifiable(), new UserInvitation($event->invitee));
Notification::send(new OwnerNotifiable(), new UserInvitation($invitee));
} catch (Exception $e) {
$message = $e->getMessage();
if (str_contains($message, 'Bcc')) {
@@ -61,6 +86,4 @@ class AdminEventHandler
Log::error($e->getTraceAsString());
}
}
// Send new version message to admin.
}

View File

@@ -29,10 +29,11 @@ use FireflyIII\Events\Security\System\SystemFoundNewVersionOnline;
use FireflyIII\Notifications\Admin\VersionCheckResult;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use FireflyIII\Support\Facades\FireflyConfig;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesOwnerAboutNewVersion
class NotifiesOwnerAboutNewVersion implements ShouldQueue
{
public function handle(SystemFoundNewVersionOnline $event): void
{

View File

@@ -28,10 +28,11 @@ use Exception;
use FireflyIII\Events\Security\System\UnknownUserTriedLogin;
use FireflyIII\Notifications\Admin\UnknownUserLoginAttempt;
use FireflyIII\Notifications\Notifiables\OwnerNotifiable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesOwnerAboutUnknownUser
class NotifiesOwnerAboutUnknownUser implements ShouldQueue
{
public function handle(UnknownUserTriedLogin $event): void
{

View File

@@ -30,10 +30,11 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\ConfirmEmailChangeMail;
use FireflyIII\Mail\UndoEmailChangeMail;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
class HandlesChangeOfUserEmailAddress
class HandlesChangeOfUserEmailAddress implements ShouldQueue
{
public function handle(UserChangedEmailAddress $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasEnabledMFA;
use FireflyIII\Notifications\Security\EnabledMFANotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutEnabledMFA
class NotifiesUserAboutEnabledMFA implements ShouldQueue
{
public function handle(UserHasEnabledMFA $event): void
{

View File

@@ -26,10 +26,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserFailedLoginAttempt;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFailedLogin
class NotifiesUserAboutFailedLogin implements ShouldQueue
{
public function handle(UserFailedLoginAttempt $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasFewMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupFewLeftNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutFewCodesLeft
class NotifiesUserAboutFewCodesLeft implements ShouldQueue
{
public function handle(UserHasFewMFABackupCodesLeft $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasGeneratedNewBackupCodes;
use FireflyIII\Notifications\Security\NewBackupCodesNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNewBackupCodes
class NotifiesUserAboutNewBackupCodes implements ShouldQueue
{
public function handle(UserHasGeneratedNewBackupCodes $event): void
{

View File

@@ -28,10 +28,11 @@ use Exception;
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
use FireflyIII\Notifications\User\UserLogin;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNewIpAddress
class NotifiesUserAboutNewIpAddress implements ShouldQueue
{
public function handle(UserLoggedInFromNewIpAddress $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasNoMFABackupCodesLeft;
use FireflyIII\Notifications\Security\MFABackupNoLeftNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutNoCodesLeft
class NotifiesUserAboutNoCodesLeft implements ShouldQueue
{
public function handle(UserHasNoMFABackupCodesLeft $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserKeepsFailingMFA;
use FireflyIII\Notifications\Security\MFAManyFailedAttemptsNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutRepeatedMFAFailures
class NotifiesUserAboutRepeatedMFAFailures implements ShouldQueue
{
public function handle(UserKeepsFailingMFA $event): void
{

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserHasUsedBackupCode;
use FireflyIII\Notifications\Security\MFAUsedBackupCodeNotification;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class NotifiesUserAboutUsedBackupCode
class NotifiesUserAboutUsedBackupCode implements ShouldQueue
{
public function handle(UserHasUsedBackupCode $event): void
{

View File

@@ -1,8 +1,10 @@
<?php
/**
* UserEventHandler.php
* Copyright (c) 2019 james@firefly-iii.org
declare(strict_types=1);
/*
* RespondsToNewLogin.php
* Copyright (c) 2026 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@@ -19,43 +21,40 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Admin\InvitationCreated;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Mail\InvitationMail;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Facades\Preferences;
use FireflyIII\User;
use Illuminate\Auth\Events\Login;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Mail;
use InvalidArgumentException;
/**
* Class UserEventHandler.
*
* This class responds to any events that have anything to do with the User object.
*
* The method name reflects what is being done. This is in the present tense.
*/
class UserEventHandler
class RespondsToNewLogin implements ShouldQueue
{
public function handle(Login $event): void
{
$user = $event->user;
if (!$user instanceof User) {
throw new InvalidArgumentException(sprintf('User cannot be an instance of %s.', get_class($user)));
}
$this->checkSingleUserIsAdmin($user);
$this->demoUserBackToEnglish($user);
}
/**
* Fires to see if a user is admin.
*/
public function checkSingleUserIsAdmin(Login $event): void
private function checkSingleUserIsAdmin(User $user): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
$count = $repository->count();
// only act when there is 1 user in the system and he has no admin rights.
// only act when there is 1 user in the system, and he has no admin rights.
if (1 === $count && !$repository->hasRole($user, 'owner')) {
// user is the only user but does not have role "owner".
$role = $repository->getRole('owner');
@@ -74,13 +73,10 @@ class UserEventHandler
/**
* Set the demo user back to English.
*/
public function demoUserBackToEnglish(Login $event): void
private function demoUserBackToEnglish(User $user): void
{
/** @var UserRepositoryInterface $repository */
$repository = app(UserRepositoryInterface::class);
/** @var User $user */
$user = $event->user;
if ($repository->hasRole($user, 'demo')) {
// set user back to English.
Preferences::setForUser($user, 'language', 'en_US');
@@ -89,23 +85,4 @@ class UserEventHandler
Preferences::mark();
}
}
/**
* @throws FireflyException
*/
public function sendRegistrationInvite(InvitationCreated $event): void
{
$invitee = $event->invitee->email;
$admin = $event->invitee->user->email;
$url = route('invite', [$event->invitee->invite_code]);
try {
Mail::to($invitee)->send(new InvitationMail($invitee, $admin, $url));
} catch (Exception $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
throw new FireflyException($e->getMessage(), 0, $e);
}
}
}

View File

@@ -27,10 +27,11 @@ namespace FireflyIII\Listeners\Security\User;
use Exception;
use FireflyIII\Events\Security\User\UserRequestedNewPassword;
use FireflyIII\Notifications\User\UserNewPassword;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Notification;
class SendsUserNewPassword
class SendsUserNewPassword implements ShouldQueue
{
public function handle(UserRequestedNewPassword $event): void
{

View File

@@ -28,9 +28,10 @@ use Carbon\Carbon;
use FireflyIII\Events\Security\User\UserLoggedInFromNewIpAddress;
use FireflyIII\Events\Security\User\UserSuccessfullyLoggedIn;
use FireflyIII\Support\Facades\Preferences;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Support\Facades\Log;
class StoresNewIpAddress
class StoresNewIpAddress implements ShouldQueue
{
public function handle(UserSuccessfullyLoggedIn $event): void
{

View File

@@ -60,8 +60,11 @@ class Account extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$accountId = (int) $value;

View File

@@ -62,8 +62,11 @@ class Attachment extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attachmentId = (int) $value;

View File

@@ -59,8 +59,11 @@ class AvailableBudget extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$availableBudgetId = (int) $value;

View File

@@ -74,8 +74,11 @@ class Bill extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$billId = (int) $value;

View File

@@ -53,8 +53,11 @@ class Budget extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetId = (int) $value;

View File

@@ -53,8 +53,11 @@ class BudgetLimit extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$budgetLimitId = (int) $value;
$budgetLimit = self::where('budget_limits.id', $budgetLimitId)

View File

@@ -52,8 +52,11 @@ class Category extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$categoryId = (int) $value;

View File

@@ -42,8 +42,11 @@ class InvitedUser extends Model
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$attemptId = (int) $value;

View File

@@ -41,8 +41,11 @@ class LinkType extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$linkTypeId = (int) $value;
$linkType = self::find($linkTypeId);

View File

@@ -45,8 +45,11 @@ class ObjectGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$objectGroupId = (int) $value;

View File

@@ -60,8 +60,11 @@ class PiggyBank extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$piggyBankId = (int) $value;
$piggyBank = self::where('piggy_banks.id', $piggyBankId)

View File

@@ -42,8 +42,11 @@ class Preference extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
/** @var User $user */
$user = auth()->user();

View File

@@ -74,8 +74,11 @@ class Recurrence extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$recurrenceId = (int) $value;

View File

@@ -52,8 +52,11 @@ class Rule extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleId = (int) $value;

View File

@@ -54,8 +54,11 @@ class RuleGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$ruleGroupId = (int) $value;

View File

@@ -52,8 +52,11 @@ class Tag extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$tagId = (int) $value;

View File

@@ -48,8 +48,11 @@ class TransactionCurrency extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$currencyId = (int) $value;
$currency = self::find($currencyId);

View File

@@ -55,8 +55,11 @@ class TransactionGroup extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
Log::debug(sprintf('Now in %s("%s")', __METHOD__, $value));
if (auth()->check()) {
$groupId = (int) $value;

View File

@@ -79,8 +79,11 @@ class TransactionJournal extends Model
*
* @throws NotFoundHttpException
*/
public static function routeBinder(string $value): self
public static function routeBinder(self|string $value): self
{
if ($value instanceof self) {
$value = (int) $value->id;
}
if (auth()->check()) {
$journalId = (int) $value;

Some files were not shown because too many files have changed in this diff Show More