Compare commits

...

19 Commits

Author SHA1 Message Date
James Cole
f7c01e6821 Restore v2 layout in dev. 2026-03-18 06:10:21 +01:00
James Cole
3a971d738c Fix https://github.com/firefly-iii/firefly-iii/issues/11976 2026-03-18 05:33:42 +01:00
James Cole
1eb4ae3a2c Fix a variety of Mago issues. 2026-03-18 05:26:50 +01:00
James Cole
0f30eb59a4 Fix https://github.com/firefly-iii/firefly-iii/issues/11978 2026-03-18 05:10:25 +01:00
James Cole
9c10b01e8b Small code fixes. 2026-03-17 20:43:32 +01:00
James Cole
7c4f80a360 Small changes based on Mago rule 2026-03-17 17:29:24 +01:00
James Cole
e5c19f6088 Fix https://github.com/firefly-iii/firefly-iii/issues/11969 2026-03-17 16:26:57 +01:00
James Cole
b067215ba8 Merge pull request #11974 from NorskNoobing/patch-1 2026-03-17 10:57:44 +01:00
Daniel Holøien
a17d10b064 Fix typo in SMTP server comment in .env.example
Signed-off-by: Daniel Holøien <39239702+NorskNoobing@users.noreply.github.com>
2026-03-17 07:52:36 +01:00
github-actions[bot]
859fea532d Merge pull request #11967 from firefly-iii/release-1773691728
🤖 Automatically merge the PR into the develop branch.
2026-03-16 21:10:01 +01:00
JC5
75261a46d9 🤖 Auto commit for release 'develop' on 2026-03-16 2026-03-16 21:08:48 +01:00
James Cole
a4a99310ea Fix bad comparison. 2026-03-16 21:02:52 +01:00
James Cole
8de0844e55 Remove old fields for tag 2026-03-16 20:54:57 +01:00
James Cole
e7a6dd792f Another fix for https://github.com/firefly-iii/firefly-iii/issues/11964 2026-03-16 20:43:05 +01:00
James Cole
395ccf8f75 Update changelog. 2026-03-16 20:27:57 +01:00
James Cole
dfbfdb6aa2 Fix https://github.com/firefly-iii/firefly-iii/issues/11964 2026-03-16 20:26:57 +01:00
James Cole
a367ee96bd Merge branch 'main' into develop 2026-03-16 20:18:25 +01:00
James Cole
b6b1261df5 Fix https://github.com/firefly-iii/firefly-iii/issues/11966 2026-03-16 20:17:52 +01:00
github-actions[bot]
490c421ae5 Merge pull request #11957 from firefly-iii/develop
🤖 Automatically merge the PR into the main branch.
2026-03-15 17:36:55 +01:00
50 changed files with 315 additions and 254 deletions

View File

@@ -173,7 +173,7 @@ MAIL_ENCRYPTION=null
MAIL_SENDMAIL_COMMAND=
#
# If you use self-signed certificates for your STMP server, you can use the following settings.
# If you use self-signed certificates for your SMTP server, you can use the following settings.
#
MAIL_ALLOW_SELF_SIGNED=false
MAIL_VERIFY_PEER=true

View File

@@ -96,7 +96,7 @@ final class AccountController extends Controller
$nameWithBalance = $account->name;
$currency = $this->repository->getAccountCurrency($account) ?? $this->primaryCurrency;
$useCurrency = $currency;
if (in_array($account->accountType->type, $this->balanceTypes, true)) {
if (in_array($account->accountType->type, $this->balanceTypes, strict: true)) {
// this one is correct.
Log::debug(sprintf('accounts: Call finalAccountBalance with date/time "%s"', $date->toIso8601String()));
$balance = $allBalances[$account->id] ?? [];

View File

@@ -83,6 +83,9 @@ final class ListController extends Controller
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
if(0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setUser($admin)
// filter on journal IDs.

View File

@@ -84,6 +84,9 @@ final class ListController extends Controller
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
if(0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setUser($admin)
// filter on journal IDs.

View File

@@ -63,7 +63,7 @@ final class AccountController extends Controller
$query = trim((string) $request->get('query'));
$field = trim((string) $request->get('field'));
$type = $request->get('type') ?? 'all';
if ('' === $query || !in_array($field, $this->validFields, true)) {
if ('' === $query || !in_array($field, $this->validFields, strict: true)) {
return response(null, 422);
}
Log::debug(sprintf('Now in account search("%s", "%s")', $field, $query));

View File

@@ -66,7 +66,7 @@ final class TransactionController extends Controller
$internalRef = (string) $request->attributes->get('internal_reference');
$notes = (string) $request->attributes->get('notes');
$description = (string) $request->attributes->get('description');
Log::debug(sprintf('Include deleted? %s', var_export($includeDeleted, true)));
Log::debug(sprintf('Include deleted? %s', var_export(value: $includeDeleted, return: true)));
if ('' !== $externalId) {
$count += $this->repository->countByMeta('external_id', $externalId, $includeDeleted);
Log::debug(sprintf('Search for transactions with external_identifier "%s", count is now %d', $externalId, $count));

View File

@@ -323,7 +323,7 @@ final class BasicController extends Controller
$today = today(config('app.timezone'));
$available = $this->abRepository->getAvailableBudgetWithCurrency($start, $end);
$budgets = $this->budgetRepository->getActiveBudgets();
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets);
$spent = $this->opsRepository->sumExpenses($start, $end, null, $budgets, null, true);
$days = (int) $today->diffInDays($end, true) + 1;
$currencies = [];

View File

@@ -38,7 +38,7 @@ class ApiRequest extends FormRequest
public function handleConfig(array $config): void
{
if (in_array('required', $config, true)) {
if (in_array('required', $config, strict: true)) {
$this->required = 'required';
}
}

View File

@@ -72,7 +72,7 @@ class GenericRequest extends FormRequest
if (in_array(
$type,
[AccountTypeEnum::ASSET->value, AccountTypeEnum::LOAN->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::MORTGAGE->value],
true
strict: true
)) {
$return->push($account);
}

View File

@@ -116,7 +116,7 @@ class StoreRequest extends FormRequest
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_currency'));
}
$type = $account->accountType->type;
if (!in_array($type, $types, true)) {
if (!in_array($type, $types, strict: true)) {
$validator->errors()->add(sprintf('accounts.%d', $index), trans('validation.invalid_account_type'));
}
}

View File

@@ -51,7 +51,7 @@ class StoreRequest extends FormRequest
{
$fields = [
'title' => ['title', 'convertString'],
'description' => ['description', 'convertString'],
'description' => ['description', 'stringWithNewlines'],
'rule_group_id' => ['rule_group_id', 'convertInteger'],
'order' => ['order', 'convertInteger'],
'rule_group_title' => ['rule_group_title', 'convertString'],

View File

@@ -52,7 +52,7 @@ class CreateRequest extends FormRequest
$responses = $this->get('responses', []);
$deliveries = $this->get('deliveries', []);
if (in_array(0, [count($triggers), count($responses), count($deliveries)], true)) {
if (in_array(0, [count($triggers), count($responses), count($deliveries)], strict: true)) {
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
}

View File

@@ -53,7 +53,7 @@ class UpdateRequest extends FormRequest
$responses = $this->get('responses', []);
$deliveries = $this->get('deliveries', []);
if (in_array(0, [count($triggers), count($responses), count($deliveries)], true)) {
if (in_array(0, [count($triggers), count($responses), count($deliveries)], strict: true)) {
throw new FireflyException('Unexpectedly got no responses, triggers or deliveries.');
}

View File

@@ -56,10 +56,10 @@ class UpdateRequest extends FormRequest
public function getAll(): array
{
$name = $this->route()->parameter('dynamicConfigKey');
if (in_array($name, $this->booleans, true)) {
if (in_array($name, $this->booleans, strict: true)) {
return ['value' => $this->boolean('value')];
}
if (in_array($name, $this->integers, true)) {
if (in_array($name, $this->integers, strict: true)) {
return ['value' => $this->convertInteger('value')];
}
@@ -73,13 +73,13 @@ class UpdateRequest extends FormRequest
{
$name = $this->route()->parameter('configName');
if (in_array($name, $this->booleans, true)) {
if (in_array($name, $this->booleans, strict: true)) {
return ['value' => ['required', new IsBoolean()]];
}
if ('configuration.permission_update_check' === $name) {
return ['value' => 'required|numeric|min:-1|max:1'];
}
if (in_array($name, $this->integers, true)) {
if (in_array($name, $this->integers, strict: true)) {
return ['value' => 'required|numeric|min:464272080'];
}

View File

@@ -125,7 +125,7 @@ class CorrectsAccountTypes extends Command
private function canCreateDestination(array $validDestinations): bool
{
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, true);
return in_array(AccountTypeEnum::EXPENSE->value, $validDestinations, strict: true);
}
/**
@@ -133,7 +133,7 @@ class CorrectsAccountTypes extends Command
*/
private function canCreateSource(array $validSources): bool
{
return in_array(AccountTypeEnum::REVENUE->value, $validSources, true);
return in_array(AccountTypeEnum::REVENUE->value, $validSources, strict: true);
}
private function fixJournal(TransactionJournal $journal, string $transactionType, Transaction $source, Transaction $dest): void
@@ -308,7 +308,7 @@ class CorrectsAccountTypes extends Command
private function hasValidAccountType(array $validTypes, string $accountType): bool
{
return in_array($accountType, $validTypes, true);
return in_array($accountType, $validTypes, strict: true);
}
private function inspectJournal(TransactionJournal $journal): void
@@ -342,7 +342,7 @@ class CorrectsAccountTypes extends Command
return;
}
$expectedTypes = $this->expected[$type][$sourceAccountType];
if (!in_array($destAccountType, $expectedTypes, true)) {
if (!in_array($destAccountType, $expectedTypes, strict: true)) {
Log::debug(sprintf('[b] Going to fix journal #%d', $journal->id));
$this->fixJournal($journal, $type, $sourceTransaction, $destTransaction);
}

View File

@@ -86,10 +86,10 @@ class UpgradesToGroups extends Command
private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction
{
$set = $journal->transactions->filter(static function (Transaction $subject) use ($transaction): bool {
$amount = ((float) $transaction->amount * -1) === (float) $subject->amount; // intentional float
$amount = -(float) $transaction->amount === (float) $subject->amount; // intentional float
$identifier = $transaction->identifier === $subject->identifier;
Log::debug(sprintf('Amount the same? %s', var_export($amount, true)));
Log::debug(sprintf('ID the same? %s', var_export($identifier, true)));
Log::debug(sprintf('Amount the same? %s', var_export($amount, return: true)));
Log::debug(sprintf('ID the same? %s', var_export($identifier, return: true)));
return $amount && $identifier;
});

View File

@@ -250,6 +250,7 @@ class Handler extends ExceptionHandler
'json' => request()->acceptsJson(),
'method' => request()->method(),
'headers' => $headers,
// @mago-expect lint:no-request-all
'post' => 'POST' === request()->method() ? json_encode(request()->all()) : '',
];

View File

@@ -56,9 +56,13 @@ class YearReportGenerator implements ReportGeneratorInterface
$reportType = 'default';
try {
$result = view('reports.default.year', ['accountIds' => $accountIds, 'reportType' => $reportType])
->with('start', $this->start)
->with('end', $this->end)
$result = view('reports.default.year',
[
'accountIds' => $accountIds,
'reportType' => $reportType,
'start' => $this->start,
'end' => $this->end,
])
->render()
;
} catch (Throwable $e) {

View File

@@ -63,13 +63,15 @@ class MonthReportGenerator implements ReportGeneratorInterface
// render!
try {
$result = view('reports.tag.month', ['accountIds' => $accountIds, 'reportType' => $reportType, 'tagIds' => $tagIds])
->with('start', $this->start)
->with('end', $this->end)
->with('tags', $this->tags)
->with('accounts', $this->accounts)
->render()
;
$result = view('reports.tag.month', [
'accountIds' => $accountIds,
'reportType' => $reportType,
'tagIds' => $tagIds,
'start' => $this->start,
'end' => $this->end,
'tags' => $this->tags,
'accounts' => $this->accounts,
])->render();
} catch (Throwable $e) {
Log::error(sprintf('Cannot render reports.tag.month: %s', $e->getMessage()));
Log::error($e->getTraceAsString());

View File

@@ -955,9 +955,9 @@ trait MetaCollection
$this->fields[] = 'tags.tag as tag_name';
$this->fields[] = 'tags.date as tag_date';
$this->fields[] = 'tags.description as tag_description';
$this->fields[] = 'tags.latitude as tag_latitude';
$this->fields[] = 'tags.longitude as tag_longitude';
$this->fields[] = 'tags.zoomLevel as tag_zoom_level';
// $this->fields[] = 'tags.latitude as tag_latitude';
// $this->fields[] = 'tags.longitude as tag_longitude';
// $this->fields[] = 'tags.zoomLevel as tag_zoom_level';
$this->joinTagTables();

View File

@@ -132,7 +132,7 @@ final class NotificationController extends Controller
return redirect(route('settings.notification.index'));
}
$all = $request->all();
$all = $request->only(['channel']);
$channel = $all['test_submit'] ?? '';
switch ($channel) {

View File

@@ -83,8 +83,8 @@ final class RegisterController extends Controller
throw new FireflyException('Registration is currently not available :(');
}
$this->validator($request->all())->validate();
$user = $this->createUser($request->all());
$this->validator($request->only(['email', 'password']))->validate();
$user = $this->createUser($request->only(['email', 'password']));
Log::info(sprintf('Registered new user %s', $user->email));
$owner = new OwnerNotifiable();
event(new NewUserRegistered($owner, $user));

View File

@@ -132,7 +132,7 @@ final class ResetPasswordController extends Controller
$allowRegistration = false;
}
return view('auth.passwords.reset')->with([
return view('auth.passwords.reset', [
'token' => $token,
'email' => $request->email,
'allowRegistration' => $allowRegistration,

View File

@@ -137,15 +137,15 @@ final class BudgetLimitController extends Controller
*/
public function store(Request $request): JsonResponse|RedirectResponse
{
Log::debug('Going to store new budget-limit.', $request->all());
Log::debug('Going to store new budget-limit.');
// first search for existing one and update it if necessary.
$currency = $this->currencyRepos->find((int) $request->get('transaction_currency_id'));
$budget = $this->repository->find((int) $request->get('budget_id'));
$currency = $this->currencyRepos->find((int) $request->input('transaction_currency_id'));
$budget = $this->repository->find((int) $request->input('budget_id'));
if (!$currency instanceof TransactionCurrency || !$budget instanceof Budget) {
throw new FireflyException('No valid currency or budget.');
}
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
$start = Carbon::createFromFormat('Y-m-d', $request->input('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->input('end'));
if (!$start instanceof Carbon || !$end instanceof Carbon) {
return response()->json();

View File

@@ -302,7 +302,7 @@ final class DebugController extends Controller
}
return [
'debug' => var_export(config('app.debug'), true),
'debug' => var_export(config('app.debug'), return: true),
'audit_log_channel' => implode(', ', config('logging.channels.audit.channels')),
'default_language' => (string) config('firefly.default_language'),
'default_locale' => (string) config('firefly.default_locale'),

View File

@@ -106,7 +106,7 @@ final class HomeController extends Controller
}
$request->session()->put('is_custom_range', $isCustomRange);
Log::debug(sprintf('Set is_custom_range to %s', var_export($isCustomRange, true)));
Log::debug(sprintf('Set is_custom_range to %s', var_export($isCustomRange, return: true)));
$request->session()->put('start', $start);
Log::debug(sprintf('Set start to %s', $start->format('Y-m-d H:i:s')));
$request->session()->put('end', $end);

View File

@@ -116,7 +116,7 @@ final class JavascriptController extends Controller
'currencyCode' => $currency->code,
'currencySymbol' => $currency->symbol,
'accountingLocaleInfo' => $accounting,
'anonymous' => var_export(Steam::anonymous(), true),
'anonymous' => var_export(Steam::anonymous(), return: true),
'language' => $lang,
'dateRangeTitle' => $dateRange['title'],
'locale' => $locale,

View File

@@ -82,7 +82,7 @@ final class IntroController extends Controller
Log::debug('Elements is array', $elements);
Log::debug('Keys is', array_keys($elements));
Log::debug(sprintf('Keys has "outro": %s', var_export($hasStep, true)));
Log::debug(sprintf('Keys has "outro": %s', var_export($hasStep, return: true)));
return $hasStep;
}

View File

@@ -135,7 +135,7 @@ final class AmountController extends Controller
*/
public function postAdd(Request $request, PiggyBank $piggyBank): RedirectResponse
{
$data = $request->all();
$data = $request->only(['amount']);
$amounts = $data['amount'] ?? [];
$total = '0';
Log::debug('Start with loop.');

View File

@@ -44,7 +44,6 @@ use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use JsonException;
use Safe\Exceptions\FilesystemException;
use function Safe\file_get_contents;
use function Safe\json_decode;
@@ -61,7 +60,7 @@ final class PreferencesController extends Controller
parent::__construct();
$this->middleware(static function ($request, $next) {
app('view')->share('title', (string) trans('firefly.preferences'));
app('view')->share('title', (string)trans('firefly.preferences'));
app('view')->share('mainTitleIcon', 'fa-gear');
return $next($request);
@@ -76,22 +75,22 @@ final class PreferencesController extends Controller
* @throws FireflyException
* @throws FilesystemException
*/
public function index(AccountRepositoryInterface $repository): Factory|\Illuminate\Contracts\View\View
public function index(AccountRepositoryInterface $repository): Factory | \Illuminate\Contracts\View\View
{
$accounts = $repository->getAccountsByType([
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::ASSET->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::DEBT->value,
AccountTypeEnum::MORTGAGE->value,
]);
$isDocker = config('firefly.is_docker');
$groupedAccounts = [];
$accounts = $repository->getAccountsByType([
AccountTypeEnum::DEFAULT->value,
AccountTypeEnum::ASSET->value,
AccountTypeEnum::LOAN->value,
AccountTypeEnum::DEBT->value,
AccountTypeEnum::MORTGAGE->value,
]);
$isDocker = config('firefly.is_docker');
$groupedAccounts = [];
/** @var Account $account */
foreach ($accounts as $account) {
$type = $account->accountType->type;
$role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));
$type = $account->accountType->type;
$role = sprintf('opt_group_%s', $repository->getMetaValue($account, 'account_role'));
if (in_array($type, [AccountTypeEnum::MORTGAGE->value, AccountTypeEnum::DEBT->value, AccountTypeEnum::LOAN->value], true)) {
$role = sprintf('opt_group_l_%s', $type);
@@ -100,48 +99,48 @@ final class PreferencesController extends Controller
if ('opt_group_' === $role) {
$role = 'opt_group_defaultAsset';
}
$groupedAccounts[(string) trans(sprintf('firefly.%s', $role))][$account->id] = $account->name;
$groupedAccounts[(string)trans(sprintf('firefly.%s', $role))][$account->id] = $account->name;
}
ksort($groupedAccounts);
/** @var array<int, int> $accountIds */
$accountIds = $accounts->pluck('id')->toArray();
$viewRange = Navigation::getViewRange(false);
$frontpageAccountsPref = Preferences::get('frontpageAccounts', $accountIds);
$frontpageAccounts = $frontpageAccountsPref->data;
$accountIds = $accounts->pluck('id')->toArray();
$viewRange = Navigation::getViewRange(false);
$frontpageAccountsPref = Preferences::get('frontpageAccounts', $accountIds);
$frontpageAccounts = $frontpageAccountsPref->data;
if (!is_array($frontpageAccounts)) {
$frontpageAccounts = $accountIds;
}
$language = Steam::getLanguage();
$languages = config('firefly.languages');
$locale = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data;
$listPageSize = Preferences::get('listPageSize', 50)->data;
$darkMode = Preferences::get('darkMode', 'browser')->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$convertToPrimary = $this->convertToPrimary;
$language = Steam::getLanguage();
$languages = config('firefly.languages');
$locale = Preferences::get('locale', config('firefly.default_locale', 'equal'))->data;
$listPageSize = Preferences::get('listPageSize', 50)->data;
$darkMode = Preferences::get('darkMode', 'browser')->data;
$customFiscalYear = Preferences::get('customFiscalYear', 0)->data;
$fiscalYearStartStr = Preferences::get('fiscalYearStart', '01-01')->data;
$convertToPrimary = $this->convertToPrimary;
if (is_array($fiscalYearStartStr)) {
$fiscalYearStartStr = '01-01';
}
$fiscalYearStart = sprintf('%s-%s', Carbon::now()->format('Y'), (string) $fiscalYearStartStr);
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$availableDarkModes = config('firefly.available_dark_modes');
$fiscalYearStart = sprintf('%s-%s', Carbon::now()->format('Y'), (string)$fiscalYearStartStr);
$tjOptionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
$availableDarkModes = config('firefly.available_dark_modes');
// notifications settings
$slackUrl = Preferences::getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = (string) Preferences::getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string) Preferences::getEncrypted('pushover_user_token', '')->data;
$ntfyServer = Preferences::getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = (string) Preferences::getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = '1' === Preferences::get('ntfy_auth', false)->data;
$ntfyUser = Preferences::getEncrypted('ntfy_user', '')->data;
$ntfyPass = (string) Preferences::getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels');
$forcedAvailability = [];
$anonymous = Steam::anonymous();
$slackUrl = Preferences::getEncrypted('slack_webhook_url', '')->data;
$pushoverAppToken = (string)Preferences::getEncrypted('pushover_app_token', '')->data;
$pushoverUserToken = (string)Preferences::getEncrypted('pushover_user_token', '')->data;
$ntfyServer = Preferences::getEncrypted('ntfy_server', 'https://ntfy.sh')->data;
$ntfyTopic = (string)Preferences::getEncrypted('ntfy_topic', '')->data;
$ntfyAuth = '1' === Preferences::get('ntfy_auth', false)->data;
$ntfyUser = Preferences::getEncrypted('ntfy_user', '')->data;
$ntfyPass = (string)Preferences::getEncrypted('ntfy_pass', '')->data;
$channels = config('notifications.channels');
$forcedAvailability = [];
$anonymous = Steam::anonymous();
// notification preferences
$notifications = [];
$notifications = [];
foreach (config('notifications.notifications.user') as $key => $info) {
if (true === $info['enabled']) {
$notifications[$key] = [
@@ -167,7 +166,7 @@ final class PreferencesController extends Controller
Log::error($e->getMessage());
$locales = [];
}
$locales = ['equal' => (string) trans('firefly.equal_to_language')] + $locales;
$locales = ['equal' => (string)trans('firefly.equal_to_language')] + $locales;
// an important fallback is that the frontPageAccount array gets refilled automatically
// when it turns up empty.
if (0 === count($frontpageAccounts)) {
@@ -231,16 +230,17 @@ final class PreferencesController extends Controller
Log::debug('postIndex for preferences.');
// front page accounts
$frontpageAccounts = [];
if (is_array($request->get('frontpageAccounts')) && count($request->get('frontpageAccounts')) > 0) {
foreach ($request->get('frontpageAccounts') as $id) {
$frontpageAccounts[] = (int) $id;
if (is_array($request->input('frontpageAccounts')) && count($request->input('frontpageAccounts')) > 0) {
foreach ($request->input('frontpageAccounts') as $id) {
$frontpageAccounts[] = (int)$id;
}
Log::debug('Update frontpageAccounts', $frontpageAccounts);
Preferences::set('frontpageAccounts', $frontpageAccounts);
}
// extract notifications:
$all = $request->all();
$keys = array_map(function(string $value): string {return sprintf('notification_%s', $value);}, array_keys(config('notifications.notifications.user')));
$all = $request->only($keys);
foreach (config('notifications.notifications.user') as $key => $info) {
$key = sprintf('notification_%s', $key);
if (array_key_exists($key, $all)) {
@@ -252,10 +252,11 @@ final class PreferencesController extends Controller
Preferences::set($key, false);
}
}
unset($all);
// view range:
Log::debug(sprintf('Let viewRange to "%s"', $request->get('viewRange')));
Preferences::set('viewRange', $request->get('viewRange'));
Log::debug(sprintf('Let viewRange to "%s"', $request->input('viewRange')));
Preferences::set('viewRange', $request->input('viewRange'));
// forget session values:
session()->forget('start');
session()->forget('end');
@@ -264,6 +265,7 @@ final class PreferencesController extends Controller
// notification settings, cannot be set by the demo user.
if (!auth()->user()->hasRole('demo')) {
$variables = ['slack_webhook_url', 'pushover_app_token', 'pushover_user_token', 'ntfy_server', 'ntfy_topic', 'ntfy_user', 'ntfy_pass'];
$all = $request->only($variables);
foreach ($variables as $variable) {
if ('' === $all[$variable]) {
Preferences::delete($variable);
@@ -274,9 +276,10 @@ final class PreferencesController extends Controller
}
Preferences::set('ntfy_auth', $all['ntfy_auth'] ?? false);
}
unset($all);
// convert primary
$convertToPrimary = 1 === (int) $request->get('convertToPrimary');
$convertToPrimary = 1 === (int)$request->input('convertToPrimary');
if ($convertToPrimary && !$this->convertToPrimary) {
// set to true!
Log::debug('User sets convertToPrimary to true.');
@@ -288,9 +291,9 @@ final class PreferencesController extends Controller
Preferences::set('convert_to_primary', $convertToPrimary);
// custom fiscal year
$customFiscalYear = 1 === (int) $request->get('customFiscalYear');
$customFiscalYear = 1 === (int)$request->input('customFiscalYear');
Preferences::set('customFiscalYear', $customFiscalYear);
$fiscalYearString = (string) $request->get('fiscalYearStart');
$fiscalYearString = (string)$request->input('fiscalYearStart');
if ('' !== $fiscalYearString) {
$fiscalYearStart = Carbon::parse($fiscalYearString, config('app.timezone'))->format('m-d');
Preferences::set('fiscalYearStart', $fiscalYearStart);
@@ -298,15 +301,15 @@ final class PreferencesController extends Controller
// save page size:
Preferences::set('listPageSize', 50);
$listPageSize = (int) $request->get('listPageSize');
$listPageSize = (int)$request->input('listPageSize');
if ($listPageSize > 0 && $listPageSize < 1337) {
Preferences::set('listPageSize', $listPageSize);
}
// language:
/** @var Preference $currentLang */
$currentLang = Preferences::get('language', 'en_US');
$lang = $request->get('language');
$currentLang = Preferences::get('language', 'en_US');
$lang = $request->input('language');
if (array_key_exists($lang, config('firefly.languages'))) {
Preferences::set('language', $lang);
}
@@ -317,14 +320,14 @@ final class PreferencesController extends Controller
// same for locale:
if (!auth()->user()->hasRole('demo')) {
$locale = (string) $request->get('locale');
$locale = (string)$request->input('locale');
$locale = '' === $locale ? null : $locale;
Preferences::set('locale', $locale);
}
// optional fields for transactions:
$setOptions = $request->get('tj') ?? [];
$optionalTj = [
$setOptions = $request->input('tj') ?? [];
$optionalTj = [
'interest_date' => array_key_exists('interest_date', $setOptions),
'book_date' => array_key_exists('book_date', $setOptions),
'process_date' => array_key_exists('process_date', $setOptions),
@@ -341,17 +344,17 @@ final class PreferencesController extends Controller
Preferences::set('transaction_journal_optional_fields', $optionalTj);
// dark mode
$darkMode = $request->get('darkMode') ?? 'browser';
$darkMode = $request->input('darkMode') ?? 'browser';
if (in_array($darkMode, config('firefly.available_dark_modes'), true)) {
Preferences::set('darkMode', $darkMode);
}
// anonymous amounts?
$anonymous = '1' === $request->get('anonymous');
$anonymous = '1' === $request->input('anonymous');
Preferences::set('anonymous', $anonymous);
// save and continue
session()->flash('success', (string) trans('firefly.saved_preferences'));
session()->flash('success', (string)trans('firefly.saved_preferences'));
Preferences::mark();
Log::debug('Done saving settings.');
@@ -360,12 +363,12 @@ final class PreferencesController extends Controller
public function testNotification(Request $request): mixed
{
$all = $request->all();
$all = $request->only(['channel']);
$channel = $all['channel'] ?? '';
switch ($channel) {
default:
session()->flash('error', (string) trans('firefly.notification_test_failed', ['channel' => $channel]));
session()->flash('error', (string)trans('firefly.notification_test_failed', ['channel' => $channel]));
break;
@@ -377,7 +380,7 @@ final class PreferencesController extends Controller
$user = auth()->user();
Log::debug(sprintf('Now in testNotification("%s") controller.', $channel));
event(new UserTestsNotificationChannel($channel, $user));
session()->flash('success', (string) trans('firefly.notification_test_executed', ['channel' => $channel]));
session()->flash('success', (string)trans('firefly.notification_test_executed', ['channel' => $channel]));
}
return '';

View File

@@ -48,7 +48,7 @@ final class TagController extends Controller
{
use PeriodOverview;
protected TagRepositoryInterface $repository;
protected TagRepositoryInterface $repository;
private AttachmentHelperInterface $attachmentsHelper;
/**
@@ -60,7 +60,7 @@ final class TagController extends Controller
$this->redirectUrl = route('tags.index');
$this->middleware(function ($request, $next) {
app('view')->share('title', (string) trans('firefly.tags'));
app('view')->share('title', (string)trans('firefly.tags'));
app('view')->share('mainTitleIcon', 'fa-tag');
$this->attachmentsHelper = app(AttachmentHelperInterface::class);
@@ -75,14 +75,14 @@ final class TagController extends Controller
*
* @return Factory|View
*/
public function create(Request $request): Factory|\Illuminate\Contracts\View\View
public function create(Request $request): Factory | \Illuminate\Contracts\View\View
{
$subTitle = (string) trans('firefly.new_tag');
$subTitle = (string)trans('firefly.new_tag');
$subTitleIcon = 'fa-tag';
// location info:
$hasOldInput = null !== $request->old('_token');
$locations = ['location' => [
$hasOldInput = null !== $request->old('_token');
$locations = ['location' => [
'latitude' => $hasOldInput ? old('location_latitude') : config('firefly.default_location.latitude'),
'longitude' => $hasOldInput ? old('location_longitude') : config('firefly.default_location.longitude'),
'zoom_level' => $hasOldInput ? old('location_zoom_level') : config('firefly.default_location.zoom_level'),
@@ -103,9 +103,9 @@ final class TagController extends Controller
*
* @return Factory|View
*/
public function delete(Tag $tag): Factory|\Illuminate\Contracts\View\View
public function delete(Tag $tag): Factory | \Illuminate\Contracts\View\View
{
$subTitle = (string) trans('breadcrumbs.delete_tag', ['tag' => $tag->tag]);
$subTitle = (string)trans('breadcrumbs.delete_tag', ['tag' => $tag->tag]);
// put previous url in session
$this->rememberPreviousUrl('tags.delete.url');
@@ -121,7 +121,7 @@ final class TagController extends Controller
$tagName = $tag->tag;
$this->repository->destroy($tag);
session()->flash('success', (string) trans('firefly.deleted_tag', ['tag' => $tagName]));
session()->flash('success', (string)trans('firefly.deleted_tag', ['tag' => $tagName]));
Preferences::mark();
return redirect($this->getPreviousUrl('tags.delete.url'));
@@ -132,17 +132,17 @@ final class TagController extends Controller
*
* @return Factory|View
*/
public function edit(Tag $tag): Factory|\Illuminate\Contracts\View\View
public function edit(Tag $tag): Factory | \Illuminate\Contracts\View\View
{
$subTitle = (string) trans('firefly.edit_tag', ['tag' => $tag->tag]);
$subTitle = (string)trans('firefly.edit_tag', ['tag' => $tag->tag]);
$subTitleIcon = 'fa-tag';
$location = $this->repository->getLocation($tag);
$latitude = $location instanceof Location ? $location->latitude : config('firefly.default_location.latitude');
$longitude = $location instanceof Location ? $location->longitude : config('firefly.default_location.longitude');
$zoomLevel = $location instanceof Location ? $location->zoom_level : config('firefly.default_location.zoom_level');
$hasLocation = $location instanceof Location;
$locations = ['location' => [
$location = $this->repository->getLocation($tag);
$latitude = $location instanceof Location ? $location->latitude : config('firefly.default_location.latitude');
$longitude = $location instanceof Location ? $location->longitude : config('firefly.default_location.longitude');
$zoomLevel = $location instanceof Location ? $location->zoom_level : config('firefly.default_location.zoom_level');
$hasLocation = $location instanceof Location;
$locations = ['location' => [
'latitude' => old('location_latitude') ?? $latitude,
'longitude' => old('location_longitude') ?? $longitude,
'zoom_level' => old('location_zoom_level') ?? $zoomLevel,
@@ -163,12 +163,12 @@ final class TagController extends Controller
*
* @return Factory|View
*/
public function index(TagRepositoryInterface $repository): Factory|\Illuminate\Contracts\View\View
public function index(TagRepositoryInterface $repository): Factory | \Illuminate\Contracts\View\View
{
// start with the oldest tag
$first = session('first', today()) ?? today();
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
$first = session('first', today()) ?? today();
$oldestTagDate = $repository->oldestTag() instanceof Tag ? $repository->oldestTag()->date : clone $first;
$newestTagDate = $repository->newestTag() instanceof Tag ? $repository->newestTag()->date : today();
$oldestTagDate->startOfYear();
$newestTagDate->endOfYear();
@@ -185,22 +185,22 @@ final class TagController extends Controller
$tags[$year] = $repository->getTagsInYear($year);
$newestTagDate->subYear();
}
$count = $repository->count();
$count = $repository->count();
return view('tags.index', ['tags' => $tags, 'count' => $count]);
}
public function massDestroy(Request $request): RedirectResponse
{
$tags = $request->get('tags');
$tags = $request->get('tags');
if (null === $tags || !is_array($tags)) {
session()->flash('info', (string) trans('firefly.select_tags_to_delete'));
session()->flash('info', (string)trans('firefly.select_tags_to_delete'));
return redirect(route('tags.index'));
}
$count = 0;
foreach ($tags as $tagId) {
$tagId = (int) $tagId;
$tagId = (int)$tagId;
$tag = $this->repository->find($tagId);
if ($tag instanceof Tag) {
$this->repository->destroy($tag);
@@ -221,14 +221,14 @@ final class TagController extends Controller
* @throws FireflyException
* @throws NotFoundExceptionInterface
*/
public function show(Request $request, Tag $tag, ?Carbon $start = null, ?Carbon $end = null): Factory|\Illuminate\Contracts\View\View
public function show(Request $request, Tag $tag, ?Carbon $start = null, ?Carbon $end = null): Factory | \Illuminate\Contracts\View\View
{
// default values:
$subTitleIcon = 'fa-tag';
$page = (int) $request->get('page');
$pageSize = (int) Preferences::get('listPageSize', 50)->data;
$start ??= session('start');
$end ??= session('end');
$page = (int)$request->input('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$start ??= session('start');
$end ??= session('end');
$location = $this->repository->getLocation($tag);
$attachments = $this->repository->getAttachments($tag);
$subTitle = trans('firefly.journals_in_period_for_tag', [
@@ -237,33 +237,34 @@ final class TagController extends Controller
'end' => $end->isoFormat($this->monthAndDayFormat),
]);
$startPeriod = $this->repository->firstUseDate($tag);
$startPeriod = $this->repository->firstUseDate($tag);
$startPeriod ??= today(config('app.timezone'));
$endPeriod = clone $end;
$periods = $this->getTagPeriodOverview($tag, $startPeriod, $endPeriod);
$path = route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$endPeriod = clone $end;
$periods = $this->getTagPeriodOverview($tag, $startPeriod, $endPeriod);
$path = route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
// collect transaction journal IDs in repository,
// this makes the collector faster and more accurate.
$journalIds = $this->repository->getJournalIds($tag);
$journalIds = $this->repository->getJournalIds($tag);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setRange($start, $end)
->setLimit($pageSize)
->setPage($page)
->setJournalIds($journalIds)
->withAccountInformation()
// ->setTag($tag)
->withBudgetInformation()
->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
->withAttachmentInformation();
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);
$sums = $this->repository->sumsOfTag($tag, $start, $end);
$sums = $this->repository->sumsOfTag($tag, $start, $end);
return view('tags.show', [
'tag' => $tag,
@@ -287,14 +288,14 @@ final class TagController extends Controller
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function showAll(Request $request, Tag $tag): Factory|\Illuminate\Contracts\View\View
public function showAll(Request $request, Tag $tag): Factory | \Illuminate\Contracts\View\View
{
// default values:
$subTitleIcon = 'fa-tag';
$page = (int) $request->get('page');
$pageSize = (int) Preferences::get('listPageSize', 50)->data;
$page = (int)$request->get('page');
$pageSize = (int)Preferences::get('listPageSize', 50)->data;
$periods = [];
$subTitle = (string) trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
$subTitle = (string)trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]);
$start = $this->repository->firstUseDate($tag) ?? today(config('app.timezone'));
$end = $this->repository->lastUseDate($tag) ?? today(config('app.timezone'));
$attachments = $this->repository->getAttachments($tag);
@@ -303,10 +304,13 @@ final class TagController extends Controller
// collect transaction journal IDs in repository,
// this makes the collector faster and more accurate.
$journalIds = $this->repository->getJournalIds($tag);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector = app(GroupCollectorInterface::class);
$journalIds = $this->repository->getJournalIds($tag);
if (0 === count($journalIds)) {
$collector->findNothing();
}
$collector
->setRange($start, $end)
->setLimit($pageSize)
@@ -315,11 +319,10 @@ final class TagController extends Controller
->setJournalIds($journalIds)
->withBudgetInformation()
->withCategoryInformation()
->withAttachmentInformation()
;
$groups = $collector->getPaginatedGroups();
->withAttachmentInformation();
$groups = $collector->getPaginatedGroups();
$groups->setPath($path);
$sums = $this->repository->sumsOfTag($tag, $start, $end);
$sums = $this->repository->sumsOfTag($tag, $start, $end);
return view('tags.show', [
'tag' => $tag,
@@ -340,24 +343,24 @@ final class TagController extends Controller
*/
public function store(TagFormRequest $request): RedirectResponse
{
$data = $request->collectTagData();
$data = $request->collectTagData();
Log::debug('Data from request', $data);
$result = $this->repository->store($data);
$result = $this->repository->store($data);
Log::debug('Data after storage', $result->toArray());
session()->flash('success', (string) trans('firefly.created_tag', ['tag' => $data['tag']]));
session()->flash('success', (string)trans('firefly.created_tag', ['tag' => $data['tag']]));
Preferences::mark();
// store attachment(s):
/** @var null|array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachmentsHelper->saveAttachmentsForModel($result, $files);
}
if (null !== $files && auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__));
session()->flash('info', (string) trans('firefly.no_att_demo_user'));
session()->flash('info', (string)trans('firefly.no_att_demo_user'));
}
if (count($this->attachmentsHelper->getMessages()->get('attachments')) > 0) {
@@ -367,7 +370,7 @@ final class TagController extends Controller
$request->session()->flash('error', $this->attachmentsHelper->getErrors()->get('attachments'));
}
$redirect = redirect($this->getPreviousUrl('tags.create.url'));
if (1 === (int) $request->get('create_another')) {
if (1 === (int)$request->get('create_another')) {
session()->put('tags.create.fromStore', true);
$redirect = redirect(route('tags.create'))->withInput();
@@ -381,21 +384,21 @@ final class TagController extends Controller
*/
public function update(TagFormRequest $request, Tag $tag): RedirectResponse
{
$data = $request->collectTagData();
$tag = $this->repository->update($tag, $data);
$data = $request->collectTagData();
$tag = $this->repository->update($tag, $data);
session()->flash('success', (string) trans('firefly.updated_tag', ['tag' => $data['tag']]));
session()->flash('success', (string)trans('firefly.updated_tag', ['tag' => $data['tag']]));
Preferences::mark();
// store new attachment(s):
/** @var null|array $files */
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
$files = $request->hasFile('attachments') ? $request->file('attachments') : null;
if (null !== $files && !auth()->user()->hasRole('demo')) {
$this->attachmentsHelper->saveAttachmentsForModel($tag, $files);
}
if (null !== $files && auth()->user()->hasRole('demo')) {
Log::channel('audit')->warning(sprintf('The demo user is trying to upload attachments in %s.', __METHOD__));
session()->flash('info', (string) trans('firefly.no_att_demo_user'));
session()->flash('info', (string)trans('firefly.no_att_demo_user'));
}
if (count($this->attachmentsHelper->getMessages()->get('attachments')) > 0) {
@@ -405,7 +408,7 @@ final class TagController extends Controller
$request->session()->flash('error', $this->attachmentsHelper->getErrors()->get('attachments'));
}
$redirect = redirect($this->getPreviousUrl('tags.edit.url'));
if (1 === (int) $request->get('return_to_edit')) {
if (1 === (int)$request->get('return_to_edit')) {
session()->put('tags.edit.fromUpdate', true);
$redirect = redirect(route('tags.edit', [$tag->id]))->withInput(['return_to_edit' => 1]);

View File

@@ -153,7 +153,7 @@ final class ConvertController extends Controller
foreach ($group->transactionJournals as $journal) {
// catch FF exception.
try {
$this->convertJournal($journal, $destinationType, $request->all());
$this->convertJournal($journal, $destinationType, $request->only(['source_id', 'source_name', 'destination_id', 'destination_name',]));
} catch (FireflyException $e) {
session()->flash('error', $e->getMessage());

View File

@@ -115,7 +115,7 @@ final class EditController extends Controller
];
$optionalFields['external_url'] ??= false;
$optionalFields['location'] ??= false;
$optionalFields['location'] = $optionalFields['location']
$optionalFields['location'] = true === $optionalFields['location']
&& true === FireflyConfig::get('enable_external_map', config('firefly.enable_external_map'))->data;
// map info voor v2:

View File

@@ -43,6 +43,6 @@ final class EditController extends Controller
$mainTitleIcon = 'fa-book';
Log::debug(sprintf('Now at %s', __METHOD__));
return view('administrations.edit')->with(['title' => $title, 'subTitle' => $subTitle, 'mainTitleIcon' => $mainTitleIcon]);
return view('administrations.edit', ['title' => $title, 'subTitle' => $subTitle, 'mainTitleIcon' => $mainTitleIcon]);
}
}

View File

@@ -70,16 +70,17 @@ class SecureHeaders
// overrule in development mode
if (true === config('firefly.is_local_dev')) {
$ip = '192.168.96.165';
$csp = [
"default-src 'none'",
"object-src 'none'",
sprintf("script-src 'unsafe-eval' 'strict-dynamic' 'nonce-%1s'", $nonce),
// sprintf("style-src 'self' 'nonce-%1s' https://10.0.0.15:5173/", $nonce), // safe variant
"style-src 'self' 'unsafe-inline' https://10.0.0.15:5173/", // unsafe variant
sprintf("style-src 'self' 'unsafe-inline' https://%s:5173/", $ip), // unsafe variant
"base-uri 'self'",
"form-action 'self'",
"font-src 'self' data: https://10.0.0.15:5173/",
sprintf("connect-src 'self' %s https://10.0.0.15:5173/ wss://10.0.0.15:5173/", $trackingScriptSrc),
sprintf("font-src 'self' data: https://%s:5173/", $ip),
sprintf('connect-src \'self\' %1$s https://%2$s:5173/ wss://%2$s:5173/', $trackingScriptSrc, $ip),
sprintf("img-src 'self' data: 'nonce-%1s'", $nonce),
"manifest-src 'self'",
];

View File

@@ -638,9 +638,10 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
/** @var Bill $bill */
foreach ($bills as $bill) {
// Log::debug(sprintf('Bill #%d ("%s")', $bill->id, $bill->name));
/** @var Collection $set */
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToPrimary && $bill->transactionCurrency->id !== $primary->id ? $primary : $bill->transactionCurrency;
$set = $bill->transactionJournals()->after($start)->before($end)->get(['transaction_journals.*']);
$currency = $convertToPrimary && $bill->transactionCurrency->id !== $primary->id ? $primary : $bill->transactionCurrency;
$return[(int) $currency->id] ??= [
'id' => (string) $currency->id,
'name' => $currency->name,
@@ -649,13 +650,14 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
'decimal_places' => $currency->decimal_places,
'sum' => '0',
];
$setAmount = '0';
// Log::debug(sprintf('Created a new array for currency #%d', $currency->id));
/** @var TransactionJournal $transactionJournal */
foreach ($set as $transactionJournal) {
// grab currency from transaction.
$transactionCurrency = $transactionJournal->transactionCurrency;
$return[(int) $transactionCurrency->id] ??= [
// grab currency from journal.
$transactionCurrency = $transactionJournal->transactionCurrency;
$currencyId = (int) $transactionCurrency->id;
$return[$currencyId] ??= [
'id' => (string) $transactionCurrency->id,
'name' => $transactionCurrency->name,
'symbol' => $transactionCurrency->symbol,
@@ -663,19 +665,12 @@ class BillRepository implements BillRepositoryInterface, UserGroupInterface
'decimal_places' => $transactionCurrency->decimal_places,
'sum' => '0',
];
$amountFromJournal = Amount::getAmountFromJournalObject($transactionJournal);
// Log::debug(sprintf('Created a (new) array for currency #%d', $currencyId));
// Log::debug(sprintf('Amount to add is %s', $amountFromJournal));
// get currency from transaction as well.
$return[(int) $transactionCurrency->id]['sum'] = bcadd(
$return[(int) $transactionCurrency->id]['sum'],
Amount::getAmountFromJournalObject($transactionJournal)
);
// $setAmount = bcadd($setAmount, Amount::getAmountFromJournalObject($transactionJournal));
$return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $amountFromJournal);
}
// Log::debug(sprintf('Bill #%d ("%s") with %d transaction(s) and sum %s %s', $bill->id, $bill->name, $set->count(), $currency->code, $setAmount));
// $return[$currency->id]['sum'] = bcadd($return[$currency->id]['sum'], $setAmount);
// Log::debug(sprintf('Total sum is now %s', $return[$currency->id]['sum']));
}
// remove empty sets
$final = [];

View File

@@ -695,6 +695,7 @@ class JournalUpdateService
$source = $this->getSourceTransaction();
$dest = $this->getDestinationTransaction();
$foreignCurrency = $source->foreignCurrency;
$oldForeignCurrency = $foreignCurrency;
$originalSourceAmount = $source->foreign_amount;
// find currency in data array
@@ -773,11 +774,14 @@ class JournalUpdateService
$this->transactionJournal,
'update_foreign_amount',
[
'currency_symbol' => $recordCurrency->symbol,
'decimal_places' => $recordCurrency->decimal_places,
'currency_symbol' => $oldForeignCurrency->symbol,
'decimal_places' => $oldForeignCurrency->decimal_places,
'amount' => $originalSourceAmount,
],
['currency_symbol' => $recordCurrency->symbol, 'decimal_places' => $recordCurrency->decimal_places, 'amount' => $value]
[
'currency_symbol' => $recordCurrency->symbol,
'decimal_places' => $recordCurrency->decimal_places,
'amount' => $value]
)
);
}

View File

@@ -225,21 +225,27 @@ class Amount
*/
public function getAmountFromJournalObject(TransactionJournal $journal): string
{
// Log::debug(sprintf('Get amount from journal #%d', $journal->id));
$convertToPrimary = $this->convertToPrimary();
$currency = $this->getPrimaryCurrency();
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'pc_amount' : 'amount';
$field = $convertToPrimary && $currency->id !== $journal->transaction_currency_id ? 'native_amount' : 'amount';
/** @var null|Transaction $sourceTransaction */
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
if (null === $sourceTransaction) {
// Log::debug('Return zero!');
return '0';
}
$amount = $sourceTransaction->{$field} ?? '0';
// Log::debug(sprintf('Amount is %s', $amount));
if ((int) $sourceTransaction->foreign_currency_id === $currency->id) {
// use foreign amount instead!
$amount = (string) $sourceTransaction->foreign_amount; // hard coded to be foreign amount.
// Log::debug(sprintf('Amount is now %s', $amount));
}
// Log::debug(sprintf('Final return is %s', $amount));
return $amount;
}

View File

@@ -171,7 +171,7 @@ class FrontpageChartGenerator
$direction = $array['sum_float'] < 0 ? 'spent' : 'earned';
$key = sprintf('%s-%d', $direction, $array['currency_id']);
$category = $array['name'];
$amount = $array['sum_float'] < 0 ? $array['sum_float'] * -1 : $array['sum_float'];
$amount = $array['sum_float'] < 0 ? -$array['sum_float'] : $array['sum_float'];
$currencyData[$key]['entries'][$category] = $amount;
}

View File

@@ -59,9 +59,9 @@ use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use League\Csv\AbstractCsv;
use League\Csv\CannotInsertRecord;
use League\Csv\Exception;
use League\Csv\Writer;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
@@ -284,7 +284,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -353,7 +353,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -412,7 +412,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -457,7 +457,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -537,7 +537,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -704,7 +704,7 @@ class ExportDataGenerator
}
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -851,7 +851,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -883,7 +883,7 @@ class ExportDataGenerator
*/
private function exportTags(): string
{
$header = ['user_id', 'tag_id', 'created_at', 'updated_at', 'tag', 'date', 'description', 'latitude', 'longitude', 'zoom_level'];
$header = ['user_id', 'tag_id', 'created_at', 'updated_at', 'tag', 'date', 'description']; // 'latitude', 'longitude', 'zoom_level'
$tagRepos = app(TagRepositoryInterface::class);
$tagRepos->setUser($this->user);
@@ -900,14 +900,14 @@ class ExportDataGenerator
$tag->tag,
$tag->date?->format('Y-m-d'),
$tag->description,
$tag->latitude,
$tag->longitude,
$tag->zoomLevel,
// $tag->latitude,
// $tag->longitude,
// $tag->zoomLevel,
];
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {
@@ -1103,7 +1103,7 @@ class ExportDataGenerator
}
// load the CSV document from a string
$csv = AbstractCsv::fromString();
$csv = Writer::fromString();
// insert the header
try {

View File

@@ -235,7 +235,12 @@ trait ConvertsDataTypes
*/
public function stringWithNewlines(string $field): string
{
return (string) $this->clearStringKeepNewlines((string) ($this->get($field) ?? ''));
$entry = $this->get($field);
if (!is_scalar($entry)) {
return '';
}
return (string) $this->clearStringKeepNewlines((string) $entry);
}
/**

View File

@@ -81,7 +81,7 @@ class AccountTransformer extends AbstractTransformer
}
// no order for some accounts:
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], true)) {
if (!in_array(strtolower($accountType), ['liability', 'liabilities', 'asset'], strict: true)) {
$order = null;
}

View File

@@ -524,7 +524,7 @@ class User extends Authenticatable
$userGroup->id,
$userGroup->title
));
if (in_array($membership->userRole->title, $dbRolesTitles, true)) {
if (in_array($membership->userRole->title, $dbRolesTitles, strict: true)) {
Log::debug(sprintf('Return true, found role "%s"', $membership->userRole->title));
return true;

View File

@@ -3,6 +3,39 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).
## v6.5.7 - 2026-03-xx
<!-- summary: If you can read this I forgot to update the summary! -->
### Added
- Initial release.
### Changed
- Initial release.
### Deprecated
- Initial release.
### Removed
- Initial release.
### Fixed
- [Issue 11964](https://github.com/firefly-iii/firefly-iii/issues/11964) ("Left to spend" is not taking into account non-main currency withdrawals (when displaying in primary currency)) reported by @absdjfh
- [Issue 11966](https://github.com/firefly-iii/firefly-iii/issues/11966) (Error when trying to export data in CSV (Export data via front end)) reported by @jgmm81
### Security
- Initial release.
### API
- Initial release.
## v6.5.6 - 2026-03-16
<!-- summary: This release takes note of some security issues, and fixes interesting bugs. -->

22
composer.lock generated
View File

@@ -11430,11 +11430,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.40",
"version": "2.1.41",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
"reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/a2eae8f20856b3afe74bf1f9726ce8c11438e300",
"reference": "a2eae8f20856b3afe74bf1f9726ce8c11438e300",
"shasum": ""
},
"require": {
@@ -11479,7 +11479,7 @@
"type": "github"
}
],
"time": "2026-02-23T15:04:35+00:00"
"time": "2026-03-16T18:24:10+00:00"
},
{
"name": "phpstan/phpstan-deprecation-rules",
@@ -12036,21 +12036,21 @@
},
{
"name": "rector/rector",
"version": "2.3.8",
"version": "2.3.9",
"source": {
"type": "git",
"url": "https://github.com/rectorphp/rector.git",
"reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c"
"reference": "917842143fd9f5331a2adefc214b8d7143bd32c4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rectorphp/rector/zipball/bbd37aedd8df749916cffa2a947cfc4714d1ba2c",
"reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c",
"url": "https://api.github.com/repos/rectorphp/rector/zipball/917842143fd9f5331a2adefc214b8d7143bd32c4",
"reference": "917842143fd9f5331a2adefc214b8d7143bd32c4",
"shasum": ""
},
"require": {
"php": "^7.4|^8.0",
"phpstan/phpstan": "^2.1.38"
"phpstan/phpstan": "^2.1.40"
},
"conflict": {
"rector/rector-doctrine": "*",
@@ -12084,7 +12084,7 @@
],
"support": {
"issues": "https://github.com/rectorphp/rector/issues",
"source": "https://github.com/rectorphp/rector/tree/2.3.8"
"source": "https://github.com/rectorphp/rector/tree/2.3.9"
},
"funding": [
{
@@ -12092,7 +12092,7 @@
"type": "github"
}
],
"time": "2026-02-22T09:45:50+00:00"
"time": "2026-03-16T09:43:55+00:00"
},
{
"name": "sebastian/cli-parser",

View File

@@ -79,7 +79,7 @@ return [
// see cer.php for exchange rates feature flag.
],
'version' => 'develop/2026-03-16',
'build_time' => 1773633747,
'build_time' => 1773691523,
'api_version' => '2.1.0', // field is no longer used.
'db_version' => 28, // field is no longer used.

View File

@@ -24,18 +24,12 @@ integrations = ["symfony", "laravel", "phpunit"]
excludes = ["app/Providers/AppServiceProvider.php"] # Additionally excluded from linter only
[linter.rules]
ambiguous-function-call = { enabled = false }
literal-named-argument = { enabled = false }
halstead = { effort-threshold = 7000, enabled = false }
prefer-early-continue = { enabled = false }
cyclomatic-complexity = { enabled = false }
middleware-in-routes = { enabled = false }
too-many-methods = { enabled = false }
kan-defect = { enabled = false }
no-request-all = { enabled = false }
tagged-todo = { enabled = false }
too-many-properties = { enabled = false }
prefer-view-array = { enabled = false }
halstead = { effort-threshold = 250000, volume-threshold = 200000, difficulty-threshold = 1000, enabled = true } # way too high
cyclomatic-complexity = { enabled = true, threshold = 255 } # way too high
kan-defect = { enabled = true, threshold = 20 } # way too high
too-many-properties = { enabled = true, threshold = 25 } # way too high
no-request-all = { enabled = true }
prefer-view-array = { enabled = false } # to be continued.
assertion-style = { enabled = false }
no-boolean-flag-parameter = { enabled = false }
prefer-static-closure = { enabled = false }
@@ -43,9 +37,14 @@ no-literal-password = { enabled = false }
too-many-enum-cases = { enabled = false }
tagged-fixme = { enabled = false }
excessive-parameter-list = { enabled = false }
no-redundant-math={ enabled = false }
prefer-first-class-callable={ enabled = false }
prefer-arrow-function = { enabled = false }
ambiguous-function-call = { enabled = false } # very specific rule.
literal-named-argument = { enabled = false } # insanely specific.
prefer-early-continue = { enabled = false } # does NOT improve readability.
middleware-in-routes = { enabled = false } # I like to do this.
too-many-methods = { enabled = false, threshold = 100 } # arbitrary nonsense.
tagged-todo = { enabled = false } # weird rule

7
package-lock.json generated
View File

@@ -4,6 +4,7 @@
"requires": true,
"packages": {
"": {
"name": "firefly-iii",
"hasInstallScript": true,
"workspaces": [
"resources/assets/v1",
@@ -11004,9 +11005,9 @@
}
},
"node_modules/terser": {
"version": "5.46.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz",
"integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==",
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {

View File

@@ -23,8 +23,6 @@ import laravel from 'laravel-vite-plugin';
import manifestSRI from 'vite-plugin-manifest-sri';
import * as fs from "fs";
const host = '127.0.0.1';
function manualChunks(id) {
if (id.includes('node_modules')) {
return 'vendor';
@@ -84,7 +82,7 @@ export default defineConfig(({command, mode, isSsrBuild, isPreview}) => {
server: {
cors: true,
// make sure this IP matches the IP of the dev machine.
origin: 'https://192.168.96.162:5173',
origin: 'https://192.168.96.165:5173',
watch: {
usePolling: true,
},