Merge branch 'release/5.7.0'

This commit is contained in:
James Cole 2022-04-03 12:13:35 +02:00
commit e6854b9265
1073 changed files with 36364 additions and 22411 deletions

View File

@ -142,7 +142,6 @@ MAIL_ENCRYPTION=null
MAILGUN_DOMAIN=
MAILGUN_SECRET=
# If you are on EU region in mailgun, use api.eu.mailgun.net, otherwise use api.mailgun.net
# If you use Docker or similar, you can set this variable from a file by appending it with _FILE
MAILGUN_ENDPOINT=api.mailgun.net
@ -176,42 +175,14 @@ MAP_DEFAULT_ZOOM=6
#
# Firefly III supports a few authentication methods:
# - 'web' (default, uses built in DB)
# - 'ldap'
# - 'remote_user_guard' for Authelia etc
# Read more about these settings in the documentation.
# https://docs.firefly-iii.org/advanced-installation/authentication
#
# LDAP is no longer supported :(
#
AUTHENTICATION_GUARD=web
#
# Your LDAP server may speak a dialect. You can choose between 'OpenLDAP' and 'ActiveDirectory'
# Anything else defaults to 'ActiveDirectory'
#
LDAP_DIALECT=OpenLDAP
#
# LDAP connection settings:
#
LDAP_HOST=ldap.yourserver.com
LDAP_PORT=389
LDAP_TIMEOUT=5
LDAP_SSL=false
LDAP_TLS=false
LDAP_BASE_DN="o=something,dc=site,dc=com"
LDAP_USERNAME="uid=X,ou=,o=,dc=something,dc=com"
LDAP_PASSWORD=super_secret
LDAP_AUTH_FIELD=uid
#
# If you wish to only authenticate users from a specific group, use the base DN above.
#
# If you require extra/special filters please use the LDAP_EXTRA_FILTER with a valid DN.
#
# The extra filter will only be applied after the user is authenticated.
#
LDAP_EXTRA_FILTER=
#
# Remote user guard settings
#
@ -263,6 +234,13 @@ STATIC_CRON_TOKEN=
# However if you know what you're doing you can significantly speed up container start times.
# Set each value to true to enable, or false to disable.
# Set this to true to build all locales supported by Firefly III.
# This may take quite some time (several minutes) and is generally not recommended.
# If you wish to change or alter the list of locales, start your Docker container with
# `docker run -v locale.gen:/etc/locale.gen -e DKR_BUILD_LOCALE=true`
# and make sure your preferred locales are in your own locale.gen.
DKR_BUILD_LOCALE=false
# Check if the SQLite database exists. Can be skipped if you're not using SQLite.
# Won't significantly speed up things.
DKR_CHECK_SQLITE=true
@ -291,7 +269,6 @@ DKR_RUN_PASSPORT_INSTALL=true
# Leave the following configuration vars as is.
# Unless you like to tinker and know what you're doing.
APP_NAME=FireflyIII
ADLDAP_CONNECTION=default
BROADCAST_DRIVER=log
QUEUE_DRIVER=sync
CACHE_PREFIX=firefly

View File

@ -70,8 +70,9 @@ class AccountController extends Controller
* @param AutocompleteRequest $request
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @throws FireflyException
* @throws FireflyException
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{

View File

@ -35,6 +35,9 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\ApiSupport;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use JsonException;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Class AccountController
@ -77,6 +80,9 @@ class AccountController extends Controller
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function overview(DateRequest $request): JsonResponse
{

View File

@ -34,6 +34,8 @@ use Illuminate\Routing\Controller as BaseController;
use League\Fractal\Manager;
use League\Fractal\Serializer\JsonApiSerializer;
use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Symfony\Component\HttpFoundation\ParameterBag;
/**
@ -74,6 +76,8 @@ abstract class Controller extends BaseController
* Method to grab all parameters from the URI.
*
* @return ParameterBag
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function getParameters(): ParameterBag
{
@ -94,7 +98,7 @@ abstract class Controller extends BaseController
$obj = Carbon::parse($date);
} catch (InvalidDateException | InvalidFormatException $e) {
// don't care
Log::warn(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', (string) $date, $e->getMessage()));
Log::warning(sprintf('Ignored invalid date "%s" in API controller parameter check: %s', $date, $e->getMessage()));
}
}
$bag->set($field, $obj);

View File

@ -209,8 +209,6 @@ class ListController extends Controller
*
* @param Request $request
*
* @param Budget $budget
*
* @return JsonResponse
* @throws FireflyException
* @codeCoverageIgnore

View File

@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use JsonException;
use League\Fractal\Pagination\IlluminatePaginatorAdapter;
use League\Fractal\Resource\Collection as FractalCollection;
use League\Fractal\Resource\Item;
@ -72,6 +73,7 @@ class ShowController extends Controller
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function index(): JsonResponse
@ -106,6 +108,8 @@ class ShowController extends Controller
* @param TransactionCurrency $currency
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function show(TransactionCurrency $currency): JsonResponse
@ -130,6 +134,8 @@ class ShowController extends Controller
* Show a currency.
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function showDefault(): JsonResponse

View File

@ -33,6 +33,7 @@ use FireflyIII\Support\Http\Api\AccountFilter;
use FireflyIII\Support\Http\Api\TransactionFilter;
use FireflyIII\Transformers\CurrencyTransformer;
use Illuminate\Http\JsonResponse;
use JsonException;
use League\Fractal\Resource\Item;
/**
@ -74,6 +75,7 @@ class StoreController extends Controller
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
*/
public function store(StoreRequest $request): JsonResponse
{

View File

@ -75,6 +75,8 @@ class UpdateController extends Controller
* @param TransactionCurrency $currency
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function disable(TransactionCurrency $currency): JsonResponse
@ -108,6 +110,8 @@ class UpdateController extends Controller
* @param TransactionCurrency $currency
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function enable(TransactionCurrency $currency): JsonResponse

View File

@ -113,7 +113,7 @@ class BasicController extends Controller
// give new keys
$return = [];
foreach ($total as $entry) {
if (null === $code || (null !== $code && $code === $entry['currency_code'])) {
if (null === $code || ($code === $entry['currency_code'])) {
$return[$entry['key']] = $entry;
}
}
@ -150,7 +150,9 @@ class BasicController extends Controller
foreach ($set as $transactionJournal) {
$currencyId = (int) $transactionJournal['currency_id'];
$incomes[$currencyId] = $incomes[$currencyId] ?? '0';
$incomes[$currencyId] = bcadd($incomes[$currencyId], bcmul($transactionJournal['amount'], '-1'));
$incomes[$currencyId] = bcadd($incomes[$currencyId],
bcmul($transactionJournal['amount'], '-1')
);
$sums[$currencyId] = $sums[$currencyId] ?? '0';
$sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1'));
}
@ -362,7 +364,7 @@ class BasicController extends Controller
function (Account $account) {
$includeNetWorth = $this->accountRepository->getMetaValue($account, 'include_net_worth');
return null === $includeNetWorth ? true : '1' === $includeNetWorth;
return null === $includeNetWorth || '1' === $includeNetWorth;
}
);

View File

@ -30,6 +30,8 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\Support\Binder\EitherConfigKey;
use Illuminate\Http\JsonResponse;
use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Class ConfigurationController
@ -94,6 +96,8 @@ class ConfigurationController extends Controller
*
* @return array
* @throws FireflyException
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
private function getDynamicConfiguration(): array
{

View File

@ -81,7 +81,7 @@ class UserController extends Controller
return response()->json([], 500);
}
if ($admin->id !== $user->id && $this->repository->hasRole($admin, 'owner')) {
if ($this->repository->hasRole($admin, 'owner')) {
$this->repository->destroy($user);
return response()->json([], 204);

View File

@ -74,10 +74,24 @@ class MoveTransactionsRequest extends FormRequest
// validate start before end only if both are there.
$data = $validator->getData();
if (array_key_exists('original_account', $data) && array_key_exists('destination_account', $data)) {
$this->validateMove($validator);
}
}
);
}
/**
* @param Validator $validator
* @return void
*/
private function validateMove(Validator $validator): void {
$data = $validator->getData();
$repository = app(AccountRepositoryInterface::class);
$repository->setUser(auth()->user());
$original = $repository->find((int) $data['original_account']);
$destination = $repository->find((int) $data['destination_account']);
// not the same type:
if ($original->accountType->type !== $destination->accountType->type) {
$validator->errors()->add('title', (string) trans('validation.same_account_type'));
@ -86,6 +100,8 @@ class MoveTransactionsRequest extends FormRequest
// get currency pref:
$originalCurrency = $repository->getAccountCurrency($original);
$destinationCurrency = $repository->getAccountCurrency($destination);
// check different scenario's.
if (null === $originalCurrency xor null === $destinationCurrency) {
$validator->errors()->add('title', (string) trans('validation.same_account_currency'));
@ -100,6 +116,3 @@ class MoveTransactionsRequest extends FormRequest
}
}
}
);
}
}

View File

@ -128,7 +128,7 @@ class GenericRequest extends FormRequest
foreach ($array as $billId) {
$billId = (int) $billId;
$bill = $repository->find($billId);
if (null !== $billId) {
if (null !== $bill) {
$this->bills->push($bill);
}
}
@ -160,7 +160,7 @@ class GenericRequest extends FormRequest
foreach ($array as $budgetId) {
$budgetId = (int) $budgetId;
$budget = $repository->find($budgetId);
if (null !== $budgetId) {
if (null !== $budget) {
$this->budgets->push($budget);
}
}
@ -192,7 +192,7 @@ class GenericRequest extends FormRequest
foreach ($array as $categoryId) {
$categoryId = (int) $categoryId;
$category = $repository->find($categoryId);
if (null !== $categoryId) {
if (null !== $category) {
$this->categories->push($category);
}
}
@ -282,7 +282,7 @@ class GenericRequest extends FormRequest
foreach ($array as $tagId) {
$tagId = (int) $tagId;
$tag = $repository->find($tagId);
if (null !== $tagId) {
if (null !== $tag) {
$this->tags->push($tag);
}
}

View File

@ -50,6 +50,7 @@ class StoreRequest extends FormRequest
'name' => ['name', 'string'],
'active' => ['active', 'boolean'],
'order' => ['active', 'integer'],
'notes' => ['notes', 'string'],
// auto budget currency:
'currency_id' => ['auto_budget_currency_id', 'integer'],
@ -74,6 +75,7 @@ class StoreRequest extends FormRequest
'active' => [new IsBoolean],
'currency_id' => 'exists:transaction_currencies,id',
'currency_code' => 'exists:transaction_currencies,code',
'notes' => 'nullable|between:1,65536',
// auto budget info
'auto_budget_type' => 'in:reset,rollover,none',
'auto_budget_amount' => 'numeric|min:0|max:1000000000|required_if:auto_budget_type,reset|required_if:auto_budget_type,rollover',

View File

@ -51,6 +51,7 @@ class UpdateRequest extends FormRequest
'name' => ['name', 'string'],
'active' => ['active', 'boolean'],
'order' => ['order', 'integer'],
'notes' => ['notes', 'string'],
'currency_id' => ['auto_budget_currency_id', 'integer'],
'currency_code' => ['auto_budget_currency_code', 'string'],
'auto_budget_type' => ['auto_budget_type', 'string'],
@ -82,6 +83,7 @@ class UpdateRequest extends FormRequest
return [
'name' => sprintf('between:1,100|uniqueObjectForUser:budgets,name,%d', $budget->id),
'active' => [new IsBoolean],
'notes' => 'nullable|between:1,65536',
'auto_budget_type' => 'in:reset,rollover,none',
'auto_budget_currency_id' => 'exists:transaction_currencies,id',
'auto_budget_currency_code' => 'exists:transaction_currencies,code',

View File

@ -174,6 +174,21 @@ class StoreRequest extends FormRequest
}
}
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneAction(Validator $validator): void
{
$data = $validator->getData();
$actions = $data['actions'] ?? [];
// need at least one trigger
if (!is_countable($actions) || empty($actions)) {
$validator->errors()->add('title', (string) trans('validation.at_least_one_action'));
}
}
/**
* Adds an error to the validator when there are no ACTIVE triggers in the array of data.
*
@ -231,19 +246,4 @@ class StoreRequest extends FormRequest
$validator->errors()->add(sprintf('actions.%d.active', $inactiveIndex), (string) trans('validation.at_least_one_active_action'));
}
}
/**
* Adds an error to the validator when there are no repetitions in the array of data.
*
* @param Validator $validator
*/
protected function atLeastOneAction(Validator $validator): void
{
$data = $validator->getData();
$actions = $data['actions'] ?? [];
// need at least one trigger
if (!is_countable($actions) || empty($actions)) {
$validator->errors()->add('title', (string)trans('validation.at_least_one_action'));
}
}
}

View File

@ -57,7 +57,6 @@ class CorrectOpeningBalanceCurrencies extends Command
* Execute the console command.
*
* @return int
* @throws JsonException
*/
public function handle(): int
{
@ -136,6 +135,8 @@ class CorrectOpeningBalanceCurrencies extends Command
* @param Account $account
*
* @return TransactionCurrency
* @throws JsonException
* @throws \FireflyIII\Exceptions\FireflyException
*/
private function getCurrency(Account $account): TransactionCurrency
{

View File

@ -1,4 +1,25 @@
<?php
/*
* FixIbans.php
* Copyright (c) 2022 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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Console\Commands\Correction;

View File

@ -109,6 +109,8 @@ class DecryptDatabase extends Command
* @param string $table
*
* @return bool
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isDecrypted(string $table): bool
{

View File

@ -27,7 +27,6 @@ use Crypt;
use FireflyIII\Models\Attachment;
use Illuminate\Console\Command;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Log;
use Storage;
@ -62,10 +61,9 @@ class ScanAttachments extends Command
/** @var Attachment $attachment */
foreach ($attachments as $attachment) {
$fileName = $attachment->fileName();
try {
$encryptedContent = $disk->get($fileName);
} catch (FileNotFoundException $e) {
$this->error(sprintf('Could not find data for attachment #%d: %s', $attachment->id, $e->getMessage()));
if (null === $encryptedContent) {
Log::error(sprintf('No content for attachment #%d under filename "%s"', $attachment->id, $fileName));
continue;
}
try {

View File

@ -27,6 +27,7 @@ namespace FireflyIII\Console\Commands\Tools;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Support\Cronjobs\AutoBudgetCronjob;
use FireflyIII\Support\Cronjobs\BillWarningCronjob;
use FireflyIII\Support\Cronjobs\RecurringCronjob;
use Illuminate\Console\Command;
use InvalidArgumentException;
@ -90,6 +91,17 @@ class Cron extends Command
$this->error($e->getMessage());
}
/*
* Fire bill warning cron job
*/
try {
$this->billWarningCronJob($force, $date);
} catch (FireflyException $e) {
Log::error($e->getMessage());
Log::error($e->getTraceAsString());
$this->error($e->getMessage());
}
$this->info('More feedback on the cron jobs can be found in the log files.');
return 0;
@ -150,4 +162,32 @@ class Cron extends Command
}
}
/**
* @param bool $force
* @param Carbon|null $date
* @throws FireflyException
*/
private function billWarningCronJob(bool $force, ?Carbon $date): void
{
$autoBudget = new BillWarningCronjob;
$autoBudget->setForce($force);
// set date in cron job:
if (null !== $date) {
$autoBudget->setDate($date);
}
$autoBudget->fire();
if ($autoBudget->jobErrored) {
$this->error(sprintf('Error in "bill warnings" cron: %s', $autoBudget->message));
}
if ($autoBudget->jobFired) {
$this->error(sprintf('"Send bill warnings" cron fired: %s', $autoBudget->message));
}
if ($autoBudget->jobSucceeded) {
$this->error(sprintf('"Send bill warnings" cron ran with success: %s', $autoBudget->message));
}
}
}

View File

@ -109,6 +109,8 @@ class AccountCurrencies extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -72,6 +72,8 @@ class AppendBudgetLimitPeriods extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -86,6 +86,8 @@ class BackToJournals extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isMigrated(): bool
{
@ -97,6 +99,8 @@ class BackToJournals extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -99,6 +99,8 @@ class BudgetLimitCurrency extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -96,6 +96,8 @@ class CCLiabilities extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -77,6 +77,8 @@ class CreateGroupMemberships extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -108,6 +108,8 @@ class MigrateAttachments extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -106,6 +106,8 @@ class MigrateJournalNotes extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -84,6 +84,8 @@ class MigrateRecurrenceMeta extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -77,6 +77,8 @@ class MigrateRecurrenceType extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -75,6 +75,8 @@ class MigrateTagLocations extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -129,6 +129,8 @@ class MigrateToGroups extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isMigrated(): bool
{

View File

@ -120,6 +120,8 @@ class MigrateToRules extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -115,6 +115,8 @@ class OtherCurrenciesCorrections extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -99,6 +99,8 @@ class RenameAccountMeta extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -123,6 +123,8 @@ class TransactionIdentifier extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -68,6 +68,9 @@ class TransferCurrenciesCorrections extends Command
* Execute the console command.
*
* @return int
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function handle(): int
{
@ -134,6 +137,8 @@ class TransferCurrenciesCorrections extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -81,6 +81,8 @@ class UpgradeLiabilities extends Command
/**
* @return bool
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function isExecuted(): bool
{

View File

@ -25,7 +25,6 @@ declare(strict_types=1);
namespace FireflyIII\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Log;
use Storage;
@ -51,7 +50,7 @@ class VerifySecurityAlerts extends Command
* Execute the console command.
*
* @return int
* @throws FileNotFoundException
* @throws \League\Flysystem\FilesystemException
*/
public function handle(): int
{

View File

@ -42,7 +42,6 @@ class Kernel extends ConsoleKernel
{
$this->load(__DIR__ . '/Commands');
/** @noinspection PhpIncludeInspection */
require base_path('routes/console.php');
}

View File

@ -30,6 +30,6 @@ namespace FireflyIII\Enums;
class ClauseType
{
public const TRANSACTION = 'transaction';
public const WHERE = 'where';
public const UPDATE = 'update';
public const WHERE = 'where';
}

View File

@ -37,21 +37,16 @@ class AdminRequestedTestMessage extends Event
{
use SerializesModels;
/** @var string The users IP address */
public $ipAddress;
/** @var User The user */
public $user;
public User $user;
/**
* Create a new event instance.
*
* @param User $user
* @param string $ipAddress
*/
public function __construct(User $user, string $ipAddress)
public function __construct(User $user)
{
Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s) and IP %s!', $user->id, $user->email, $ipAddress));
Log::debug(sprintf('Triggered AdminRequestedTestMessage for user #%d (%s)', $user->id, $user->email));
$this->user = $user;
$this->ipAddress = $ipAddress;
}
}

View File

@ -40,10 +40,8 @@ class RequestedReportOnJournals
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/** @var Collection The transaction groups to report on. */
public $groups;
/** @var int The ID of the user. */
public $userId;
public Collection $groups;
public int $userId;
/**
* Create a new event instance.

View File

@ -36,14 +36,9 @@ class UserChangedEmail extends Event
{
use SerializesModels;
/** @var string The user's IP address */
public $ipAddress;
/** @var string The user's new email address */
public $newEmail;
/** @var string The user's old email address */
public $oldEmail;
/** @var User The user itself */
public $user;
public string $newEmail;
public string $oldEmail;
public User $user;
/**
* UserChangedEmail constructor.
@ -51,12 +46,10 @@ class UserChangedEmail extends Event
* @param User $user
* @param string $newEmail
* @param string $oldEmail
* @param string $ipAddress
*/
public function __construct(User $user, string $newEmail, string $oldEmail, string $ipAddress)
public function __construct(User $user, string $newEmail, string $oldEmail)
{
$this->user = $user;
$this->ipAddress = $ipAddress;
$this->oldEmail = $oldEmail;
$this->newEmail = $newEmail;
}

View File

@ -1,8 +1,8 @@
<?php
/*
* AttributeHandler.php
* Copyright (c) 2021 james@firefly-iii.org
/**
* DestroyedTransactionGroup.php
* Copyright (c) 2019 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
@ -22,23 +22,33 @@
declare(strict_types=1);
namespace FireflyIII\Ldap;
namespace FireflyIII\Events;
use FireflyIII\User as DatabaseUser;
use LdapRecord\Models\Entry;
use FireflyIII\Models\Bill;
use Illuminate\Queue\SerializesModels;
/**
* Class AttributeHandler
* Class WarnUserAboutBill.
*
* @codeCoverageIgnore
*/
class AttributeHandler
class WarnUserAboutBill extends Event
{
use SerializesModels;
public Bill $bill;
public int $diff;
public string $field;
/**
* @param Entry $ldapUser
* @param DatabaseUser $database
* @param Bill $bill
* @param string $field
* @param int $diff
*/
public function handle(Entry $ldapUser, DatabaseUser $database)
public function __construct(Bill $bill, string $field, int $diff)
{
$database->email = $ldapUser->getFirstAttribute('mail');
$database->save();
$this->bill = $bill;
$this->field = $field;
$this->diff = $diff;
}
}

View File

@ -71,9 +71,11 @@ class GracefulNotFoundHandler extends ExceptionHandler
return parent::render($request, $e);
case 'accounts.show':
case 'accounts.edit':
case 'accounts.show.all':
return $this->handleAccount($request, $e);
case 'transactions.show':
case 'transactions.edit':
return $this->handleGroup($request, $e);
case 'attachments.show':
case 'attachments.edit':
@ -119,7 +121,6 @@ class GracefulNotFoundHandler extends ExceptionHandler
$request->session()->reflash();
return redirect(route('rules.index'));
case 'transactions.edit':
case 'transactions.mass.edit':
case 'transactions.mass.delete':
case 'transactions.bulk.edit':

View File

@ -153,6 +153,12 @@ class Handler extends ExceptionHandler
$userData['id'] = auth()->user()->id;
$userData['email'] = auth()->user()->email;
}
$headers = [];
if (request()->headers) {
$headers = request()->headers->all();
}
$data = [
'class' => get_class($e),
'errorMessage' => $e->getMessage(),
@ -165,6 +171,7 @@ class Handler extends ExceptionHandler
'url' => request()->fullUrl(),
'userAgent' => request()->userAgent(),
'json' => request()->acceptsJson(),
'headers' => $headers,
];
// create job that will mail.

View File

@ -74,6 +74,7 @@ class AccountFactory
*
* @return Account
* @throws FireflyException
* @throws JsonException
*/
public function findOrCreate(string $accountName, string $accountType): Account
{
@ -182,6 +183,7 @@ class AccountFactory
* @param array $data
*
* @return Account
* @throws FireflyException
* @throws JsonException
*/
private function createAccount(AccountType $type, array $data): Account
@ -361,7 +363,6 @@ class AccountFactory
* @param array $data
*
* @throws FireflyException
* @throws JsonException
*/
private function storeOrder(Account $account, array $data): void
{

View File

@ -58,12 +58,10 @@ class AccountMetaFactory
}
// if $data has field and $entry is not null, update $entry:
if (null !== $entry) {
$entry->data = $value;
$entry->save();
Log::debug(sprintf('Updated meta-field "%s":"%s" for #%d ("%s") ', $field, $value, $account->id, $account->name));
}
}
if ('' === $value && null !== $entry) {
try {
$entry->delete();

View File

@ -46,6 +46,7 @@ class BillFactory
*
* @return Bill|null
* @throws FireflyException
* @throws \JsonException
*/
public function create(array $data): ?Bill
{

View File

@ -43,6 +43,15 @@ class TransactionCurrencyFactory
*/
public function create(array $data): TransactionCurrency
{
// if the code already exists (deleted)
// force delete it and then create the transaction:
$count = TransactionCurrency::withTrashed()->whereCode($data['code'])->count();
if (1 === $count) {
$old = TransactionCurrency::withTrashed()->whereCode($data['code'])->first();
$old->forceDelete();
Log::warning(sprintf('Force deleted old currency with ID #%d and code "%s".', $old->id, $data['code']));
}
try {
/** @var TransactionCurrency $result */
$result = TransactionCurrency::create(

View File

@ -57,6 +57,7 @@ class TransactionGroupFactory
* @return TransactionGroup
* @throws DuplicateTransactionException
* @throws FireflyException
* @throws \JsonException
*/
public function create(array $data): TransactionGroup
{

View File

@ -417,6 +417,8 @@ class TransactionJournalFactory
* @param Account $account
*
* @return TransactionCurrency
* @throws FireflyException
* @throws JsonException
*/
private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency
{

View File

@ -64,6 +64,16 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $result;
}
/**
* Return the preferred period.
*
* @return string
*/
protected function preferredPeriod(): string
{
return 'day';
}
/**
* Set accounts.
*
@ -155,14 +165,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
{
return $this;
}
/**
* Return the preferred period.
*
* @return string
*/
protected function preferredPeriod(): string
{
return 'day';
}
}

View File

@ -49,6 +49,8 @@ class MonthReportGenerator implements ReportGeneratorInterface
* Generates the report.
*
* @return string
* @throws FireflyException
* @throws JsonException
* @codeCoverageIgnore
*/
public function generate(): string
@ -90,6 +92,80 @@ class MonthReportGenerator implements ReportGeneratorInterface
return $result;
}
/**
* Get the audit report.
*
* @param Account $account
* @param Carbon $date
*
* @return array
* @throws FireflyException
* @throws JsonException
*/
#[ArrayShape(['journals' => "array", 'currency' => "mixed", 'exists' => "bool", 'end' => "string", 'endBalance' => "mixed", 'dayBefore' => "string",
'dayBeforeBalance' => "mixed"])] public function getAuditReport(Account $account, Carbon $date): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountRepository->setUser($account->user);
/** @var JournalRepositoryInterface $journalRepository */
$journalRepository = app(JournalRepositoryInterface::class);
$journalRepository->setUser($account->user);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end)->withAccountInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation();
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
$dayBeforeBalance = app('steam')->balance($account, $date);
$startBalance = $dayBeforeBalance;
$defaultCurrency = app('amount')->getDefaultCurrencyByUser($account->user);
$currency = $accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
foreach ($journals as $index => $journal) {
$journals[$index]['balance_before'] = $startBalance;
$transactionAmount = $journal['amount'];
// make sure amount is in the right "direction".
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['amount']);
}
if ($currency->id === $journal['foreign_currency_id']) {
$transactionAmount = $journal['foreign_amount'];
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['foreign_amount']);
}
}
$newBalance = bcadd($startBalance, $transactionAmount);
$journals[$index]['balance_after'] = $newBalance;
$startBalance = $newBalance;
// add meta dates for each journal.
$journals[$index]['interest_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'interest_date');
$journals[$index]['book_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'book_date');
$journals[$index]['process_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'process_date');
$journals[$index]['due_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'due_date');
$journals[$index]['payment_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'payment_date');
$journals[$index]['invoice_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'invoice_date');
}
$locale = app('steam')->getLocale();
return [
'journals' => $journals,
'currency' => $currency,
'exists' => !empty($journals),
'end' => $this->end->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'endBalance' => app('steam')->balance($account, $this->end),
'dayBefore' => $date->isoFormat((string) trans('config.month_and_day_moment_js', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];
}
/**
* Account collection setter.
*
@ -187,78 +263,4 @@ class MonthReportGenerator implements ReportGeneratorInterface
{
return $this;
}
/**
* Get the audit report.
*
* @param Account $account
* @param Carbon $date
*
* @return array
* @throws FireflyException
* @throws JsonException
*/
#[ArrayShape(['journals' => "array", 'currency' => "mixed", 'exists' => "bool", 'end' => "string", 'endBalance' => "mixed", 'dayBefore' => "string",
'dayBeforeBalance' => "mixed"])] public function getAuditReport(Account $account, Carbon $date): array
{
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accountRepository->setUser($account->user);
/** @var JournalRepositoryInterface $journalRepository */
$journalRepository = app(JournalRepositoryInterface::class);
$journalRepository->setUser($account->user);
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end)->withAccountInformation()
->withBudgetInformation()->withCategoryInformation()->withBillInformation();
$journals = $collector->getExtractedJournals();
$journals = array_reverse($journals, true);
$dayBeforeBalance = app('steam')->balance($account, $date);
$startBalance = $dayBeforeBalance;
$defaultCurrency = app('amount')->getDefaultCurrencyByUser($account->user);
$currency = $accountRepository->getAccountCurrency($account) ?? $defaultCurrency;
foreach ($journals as $index => $journal) {
$journals[$index]['balance_before'] = $startBalance;
$transactionAmount = $journal['amount'];
// make sure amount is in the right "direction".
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['amount']);
}
if ($currency->id === $journal['foreign_currency_id']) {
$transactionAmount = $journal['foreign_amount'];
if ($account->id === $journal['destination_account_id']) {
$transactionAmount = app('steam')->positive($journal['foreign_amount']);
}
}
$newBalance = bcadd($startBalance, $transactionAmount);
$journals[$index]['balance_after'] = $newBalance;
$startBalance = $newBalance;
// add meta dates for each journal.
$journals[$index]['interest_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'interest_date');
$journals[$index]['book_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'book_date');
$journals[$index]['process_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'process_date');
$journals[$index]['due_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'due_date');
$journals[$index]['payment_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'payment_date');
$journals[$index]['invoice_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'invoice_date');
}
$locale = app('steam')->getLocale();
return [
'journals' => $journals,
'currency' => $currency,
'exists' => !empty($journals),
'end' => $this->end->formatLocalized((string)trans('config.month_and_day', [], $locale)),
'endBalance' => app('steam')->balance($account, $this->end),
'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day', [], $locale)),
'dayBeforeBalance' => $dayBeforeBalance,
];
}
}

View File

@ -66,38 +66,6 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$this->run();
}
/**
* @inheritDoc
*/
public function getVersion(): int
{
return $this->version;
}
/**
* @param Collection $objects
*/
public function setObjects(Collection $objects): void
{
$this->objects = $objects;
}
/**
* @param int $trigger
*/
public function setTrigger(int $trigger): void
{
$this->trigger = $trigger;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
/**
* @return Collection
*/
@ -134,6 +102,8 @@ class StandardMessageGenerator implements MessageGeneratorInterface
/**
* @param Webhook $webhook
* @param Model $model
* @throws FireflyException
* @throws \JsonException
*/
private function generateMessage(Webhook $webhook, Model $model): void
{
@ -197,6 +167,14 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$this->storeMessage($webhook, $basicMessage);
}
/**
* @inheritDoc
*/
public function getVersion(): int
{
return $this->version;
}
/**
* @param TransactionGroup $transactionGroup
*
@ -220,9 +198,9 @@ class StandardMessageGenerator implements MessageGeneratorInterface
* @param Webhook $webhook
* @param array $message
*
* @return WebhookMessage
* @return void
*/
private function storeMessage(Webhook $webhook, array $message): WebhookMessage
private function storeMessage(Webhook $webhook, array $message): void
{
$webhookMessage = new WebhookMessage;
$webhookMessage->webhook()->associate($webhook);
@ -233,6 +211,29 @@ class StandardMessageGenerator implements MessageGeneratorInterface
$webhookMessage->save();
Log::debug(sprintf('Stored new webhook message #%d', $webhookMessage->id));
return $webhookMessage;
}
/**
* @param Collection $objects
*/
public function setObjects(Collection $objects): void
{
$this->objects = $objects;
}
/**
* @param int $trigger
*/
public function setTrigger(int $trigger): void
{
$this->trigger = $trigger;
}
/**
* @param User $user
*/
public function setUser(User $user): void
{
$this->user = $user;
}
}

View File

@ -30,7 +30,6 @@ use FireflyIII\Repositories\User\UserRepositoryInterface;
use Laravel\Passport\Events\AccessTokenCreated;
use Log;
use Mail;
use Request;
use Session;
/**
@ -59,18 +58,16 @@ class APIEventHandler
$email = config('firefly.site_owner');
}
$ipAddress = Request::ip();
// see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) {
$email = (string) (is_array($pref->data) ? $email : $pref->data);
}
Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s, IP is %s', $email, $ipAddress));
Log::debug(sprintf('Now in APIEventHandler::accessTokenCreated. Email is %s', $email));
try {
Log::debug('Trying to send message...');
Mail::to($email)->send(new AccessTokenCreatedMail($email, $ipAddress));
Mail::to($email)->send(new AccessTokenCreatedMail);
} catch (Exception $e) { // @phpstan-ignore-line
Log::debug('Send message failed! :(');

View File

@ -52,7 +52,6 @@ class AdminEventHandler
// is user even admin?
if ($repository->hasRole($event->user, 'owner')) {
$email = $event->user->email;
$ipAddress = $event->ipAddress;
// if user is demo user, send to owner:
if ($event->user->hasRole('demo')) {
@ -65,10 +64,10 @@ class AdminEventHandler
$email = $pref->data;
}
Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s, IP is %s', $email, $ipAddress));
Log::debug(sprintf('Now in sendTestMessage event handler. Email is %s', $email));
try {
Log::debug('Trying to send message...');
Mail::to($email)->send(new AdminTestMail($email, $ipAddress));
Mail::to($email)->send(new AdminTestMail($email));
// Laravel cannot pretend this process failed during testing.
} catch (Exception $e) { // @phpstan-ignore-line

View File

@ -43,7 +43,6 @@ class AutomationHandler
* @param RequestedReportOnJournals $event
*
* @return bool
* @throws FireflyException
*/
public function reportJournals(RequestedReportOnJournals $event): bool
{
@ -58,23 +57,9 @@ class AutomationHandler
$repository = app(UserRepositoryInterface::class);
$user = $repository->find($event->userId);
if (null !== $user && 0 !== $event->groups->count()) {
$email = $user->email;
// see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) {
$email = $pref->data;
}
// if user is demo user, send to owner:
if ($user->hasRole('demo')) {
$email = config('firefly.site_owner');
}
try {
Log::debug('Trying to mail...');
Mail::to($user->email)->send(new ReportNewJournalsMail($email, '127.0.0.1', $event->groups));
Mail::to($user->email)->send(new ReportNewJournalsMail($event->groups));
} catch (Exception $e) { // @phpstan-ignore-line
Log::debug('Send message failed! :(');

View File

@ -0,0 +1,64 @@
<?php
/*
* BillEventHandler.php
* Copyright (c) 2022 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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\Events\WarnUserAboutBill;
use FireflyIII\Mail\BillWarningMail;
use Log;
use Mail;
/**
* Class BillEventHandler
*/
class BillEventHandler
{
/**
* @param WarnUserAboutBill $event
* @return void
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function warnAboutBill(WarnUserAboutBill $event): void
{
$bill = $event->bill;
$field = $event->field;
$diff = $event->diff;
$user = $bill->user;
$address = $user->email;
$ipAddress = request()?->ip();
// see if user has alternative email address:
$pref = app('preferences')->getForUser($user, 'remote_guard_alt_email');
if (null !== $pref) {
$address = $pref->data;
}
// send message:
Mail::to($address)->send(new BillWarningMail($bill, $field, $diff, $ipAddress));
Log::debug('warnAboutBill');
}
}

View File

@ -1,57 +0,0 @@
<?php
/*
* LDAPEventHandler.php
* Copyright (c) 2021 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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Handlers\Events;
use FireflyIII\User;
use LdapRecord\Laravel\Events\Import\Imported;
use Log;
/**
* Class LDAPEventHandler
*/
class LDAPEventHandler
{
/**
* @param Imported $event
*/
public function importedUser(Imported $event)
{
Log::debug(sprintf('Now in %s', __METHOD__));
/** @var User $user */
$user = $event->eloquent;
$alternative = User::where('email', $user->email)->where('id', '!=', $user->id)->first();
if (null !== $alternative) {
Log::debug(sprintf('User #%d is created but user #%d already exists.', $user->id, $alternative->id));
$alternative->objectguid = $user->objectguid;
$alternative->domain = $user->domain;
$alternative->save();
$user->delete();
auth()->logout();
}
}
}

View File

@ -229,11 +229,10 @@ class UserEventHandler
$newEmail = $event->newEmail;
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_confirm_token', 'invalid');
$uri = route('profile.confirm-email-change', [$token->data]);
try {
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
Mail::to($newEmail)->send(new ConfirmEmailChangeMail($newEmail, $oldEmail, $uri));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
@ -255,12 +254,11 @@ class UserEventHandler
$newEmail = $event->newEmail;
$oldEmail = $event->oldEmail;
$user = $event->user;
$ipAddress = $event->ipAddress;
$token = app('preferences')->getForUser($user, 'email_change_undo_token', 'invalid');
$hashed = hash('sha256', sprintf('%s%s', (string) config('app.key'), $oldEmail));
$uri = route('profile.undo-email-change', [$token->data, $hashed]);
try {
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri, $ipAddress));
Mail::to($oldEmail)->send(new UndoEmailChangeMail($newEmail, $oldEmail, $uri));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
@ -334,11 +332,11 @@ class UserEventHandler
/**
* @param ActuallyLoggedIn $event
* @throws FireflyException
*/
public function storeUserIPAddress(ActuallyLoggedIn $event): void
{
Log::debug('Now in storeUserIPAddress');
/** @var User $user */
$user = $event->user;
/** @var array $preference */
try {

View File

@ -44,6 +44,8 @@ class VersionCheckEventHandler
* @param RequestedVersionCheckStatus $event
*
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function checkForUpdates(RequestedVersionCheckStatus $event): void
{
@ -90,6 +92,8 @@ class VersionCheckEventHandler
* @param RequestedVersionCheckStatus $event
*
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
protected function warnToCheckForUpdates(RequestedVersionCheckStatus $event): void
{

View File

@ -28,7 +28,6 @@ use FireflyIII\Models\Attachment;
use FireflyIII\Models\PiggyBank;
use Illuminate\Contracts\Encryption\DecryptException;
use Illuminate\Contracts\Encryption\EncryptException;
use Illuminate\Contracts\Filesystem\FileNotFoundException;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Collection;
@ -77,15 +76,10 @@ class AttachmentHelper implements AttachmentHelperInterface
*/
public function getAttachmentContent(Attachment $attachment): string
{
$encryptedData = '';
try {
$encryptedData = $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
} catch (FileNotFoundException $e) {
Log::error($e->getMessage());
}
$encryptedData = (string) $this->uploadDisk->get(sprintf('at-%d.data', $attachment->id));
try {
$unencryptedData = Crypt::decrypt($encryptedData); // verified
} catch (DecryptException | FileNotFoundException $e) {
} catch (DecryptException $e) {
Log::error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage()));
$unencryptedData = $encryptedData;
}

View File

@ -86,4 +86,61 @@ trait AmountCollection
return $this;
}
/**
* Get transactions with a specific foreign amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountIs(string $amount): GroupCollectorInterface
{
$this->query->where(
static function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('source.foreign_amount');
$q->where('source.foreign_amount', app('steam')->negative($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountLess(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('destination.foreign_amount');
$q->where('destination.foreign_amount', '<=', app('steam')->positive($amount));
}
);
return $this;
}
/**
* Get transactions where the amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountMore(string $amount): GroupCollectorInterface
{
$this->query->where(
function (EloquentBuilder $q) use ($amount) {
$q->whereNotNull('destination.foreign_amount');
$q->where('destination.foreign_amount', '>=', app('steam')->positive($amount));
}
);
return $this;
}
}

View File

@ -0,0 +1,306 @@
<?php
/*
* AttachmentCollection.php
* Copyright (c) 2022 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/>.
*/
declare(strict_types=1);
namespace FireflyIII\Helpers\Collector\Extensions;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\Attachment;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Log;
/**
* Trait AttachmentCollection
*/
trait AttachmentCollection
{
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameContains(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_contains(strtolower($attachment['filename']), strtolower($name)) || str_contains(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.filename as attachment_filename';
$this->fields[] = 'attachments.title as attachment_title';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameEnds(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_ends_with(strtolower($attachment['filename']), strtolower($name)) || str_ends_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIs(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = $attachment['filename'] === $name || $attachment['title'] === $name;
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameStarts(string $name): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($name): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
$result = str_starts_with(strtolower($attachment['filename']), strtolower($name)) || str_starts_with(strtolower($attachment['title']), strtolower($name));
if (true === $result) {
return true;
}
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAre(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && $notes === $value;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesContains(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_contains(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesEnds(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_ends_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesStarts(string $value): GroupCollectorInterface
{
$this->hasAttachments();
$this->withAttachmentInformation();
$filter = function (int $index, array $object) use ($value): bool {
/** @var array $transaction */
foreach ($object['transactions'] as $transaction) {
/** @var array $attachment */
foreach ($transaction['attachments'] as $attachment) {
/** @var Attachment $object */
$object = auth()->user()->attachments()->find($attachment['id']);
$notes = (string) $object->notes()?->first()?->text;
return $notes !== '' && str_starts_with(strtolower($notes), strtolower($value));
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* Has attachments
*
* @return GroupCollectorInterface
*/
public function hasNoAttachments(): GroupCollectorInterface
{
Log::debug('Add filter on no attachments.');
$this->joinAttachmentTables();
$this->query->whereNull('attachments.attachable_id');
return $this;
}
}

View File

@ -32,33 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/
trait CollectorProperties
{
/** @var array The standard fields to select. */
private $fields;
/** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */
private $hasAccountInfo;
/** @var bool Will be true if query result includes bill information. */
private $hasBillInformation;
/** @var bool Will be true if query result contains budget info. */
private $hasBudgetInformation;
/** @var bool Will be true if query result contains category info. */
private $hasCatInformation;
/** @var bool Will be true for attachments */
private $hasJoinedAttTables;
private array $fields;
private bool $hasAccountInfo;
private bool $hasBillInformation;
private bool $hasBudgetInformation;
private bool $hasCatInformation;
private bool $hasJoinedAttTables;
private bool $hasJoinedMetaTables;
/** @var bool Will be true of the query has the tag info tables joined. */
private $hasJoinedTagTables;
/** @var bool */
private $hasNotesInformation;
/** @var array */
private $integerFields;
/** @var int The maximum number of results. */
private $limit;
/** @var int The page to return. */
private $page;
/** @var HasMany The query object. */
private $query;
/** @var int Total number of results. */
private $total;
/** @var User The user object. */
private $user;
private bool $hasJoinedTagTables;
private bool $hasNotesInformation;
private array $integerFields;
private ?int $limit;
private ?int $page;
private array $postFilters;
private HasMany $query;
private int $total;
private ?User $user;
}

View File

@ -38,6 +38,101 @@ use Illuminate\Support\Collection;
*/
trait MetaCollection
{
/**
* @inheritDoc
*/
public function externalIdContains(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinMetaDataTables(): void
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
$this->fields[] = 'journal_meta.name as meta_name';
$this->fields[] = 'journal_meta.data as meta_data';
}
}
/**
* @inheritDoc
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlContains(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', trim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlEnds(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', ltrim($url, '"'));
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s', $url));
return $this;
}
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlStarts(string $url): GroupCollectorInterface
{
$this->joinMetaDataTables();
$url = json_encode($url);
$url = str_replace('\\', '\\\\', rtrim($url, '"'));
//var_dump($url);
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%s%%', $url));
return $this;
}
/**
* Where has no tags.
@ -52,6 +147,73 @@ trait MetaCollection
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$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->joinTagTables();
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
/**
* @inheritDoc
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s"', $externalId));
return $this;
}
/**
* @inheritDoc
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('"%s%%', $externalId));
return $this;
}
/**
* @param string $value
*
@ -65,6 +227,29 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function withNotes(): GroupCollectorInterface
{
if (false === $this->hasNotesInformation) {
// join bill table
$this->query->leftJoin(
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
$join->whereNull('notes.deleted_at');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
}
return $this;
}
/**
* @param string $value
*
@ -119,6 +304,25 @@ trait MetaCollection
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit the search to a specific set of bills.
*
@ -149,6 +353,27 @@ trait MetaCollection
return $this;
}
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/**
* Limit the search to a specific set of budgets.
*
@ -183,6 +408,27 @@ trait MetaCollection
return $this;
}
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/**
* Limit the search to a specific category.
*
@ -203,10 +449,7 @@ trait MetaCollection
*/
public function setExternalId(string $externalId): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_id');
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($externalId)));
@ -216,33 +459,11 @@ trait MetaCollection
/**
* @inheritDoc
*/
public function withoutExternalUrl(): GroupCollectorInterface
public function setExternalUrl(string $url): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhereNull('journal_meta.name');
});
return $this;
}
/**
* @inheritDoc
*/
public function withExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->whereNotNull('journal_meta.data');
$this->query->where('journal_meta.data', '=', json_encode($url));
return $this;
}
@ -252,10 +473,7 @@ trait MetaCollection
*/
public function setInternalReference(string $internalReference): GroupCollectorInterface
{
if (false === $this->hasJoinedMetaTables) {
$this->hasJoinedMetaTables = true;
$this->query->leftJoin('journal_meta', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id');
}
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'internal_reference');
$this->query->where('journal_meta.data', 'LIKE', sprintf('%%%s%%', $internalReference));
@ -263,6 +481,18 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function setRecurrenceId(string $recurringId): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'recurrence_id');
$this->query->where('journal_meta.data', '=', sprintf('%s', json_encode($recurringId)));
return $this;
}
/**
* Limit results to a specific tag.
*
@ -293,6 +523,35 @@ trait MetaCollection
return $this;
}
/**
* Without tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface
{
$this->withTagInformation();
// this method adds a "postFilter" to the collector.
$list = $tags->pluck('tag')->toArray();
$filter = function (int $index, array $object) use ($list): bool {
foreach ($object['transactions'] as $transaction) {
foreach ($transaction['tags'] as $tag) {
if (in_array($tag['name'], $list)) {
return false;
}
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @return GroupCollectorInterface
*/
@ -305,7 +564,7 @@ trait MetaCollection
}
/**
* Limit results to transactions without a bill..
* Limit results to transactions without a bill.
*
* @return GroupCollectorInterface
*/
@ -317,25 +576,6 @@ trait MetaCollection
return $this;
}
/**
* Will include bill name + ID, if any.
*
* @return GroupCollectorInterface
*/
public function withBillInformation(): GroupCollectorInterface
{
if (false === $this->hasBillInformation) {
// join bill table
$this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id');
// add fields
$this->fields[] = 'bills.id as bill_id';
$this->fields[] = 'bills.name as bill_name';
$this->hasBillInformation = true;
}
return $this;
}
/**
* Limit results to a transactions without a budget..
*
@ -349,27 +589,6 @@ trait MetaCollection
return $this;
}
/**
* Will include budget ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withBudgetInformation(): GroupCollectorInterface
{
if (false === $this->hasBudgetInformation) {
// join link table
$this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id');
// add fields
$this->fields[] = 'budgets.id as budget_id';
$this->fields[] = 'budgets.name as budget_name';
$this->hasBudgetInformation = true;
}
return $this;
}
/**
* Limit results to a transactions without a category.
*
@ -383,63 +602,14 @@ trait MetaCollection
return $this;
}
/**
* Will include category ID + name, if any.
*
* @return GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface
{
if (false === $this->hasCatInformation) {
// join link table
$this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
// join cat table
$this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id');
// add fields
$this->fields[] = 'categories.id as category_id';
$this->fields[] = 'categories.name as category_name';
$this->hasCatInformation = true;
}
return $this;
}
/**
* @inheritDoc
*/
public function withNotes(): GroupCollectorInterface
public function withExternalUrl(): GroupCollectorInterface
{
if (false === $this->hasNotesInformation) {
// join bill table
$this->query->leftJoin(
'notes',
static function (JoinClause $join) {
$join->on('notes.noteable_id', '=', 'transaction_journals.id');
$join->where('notes.noteable_type', '=', 'FireflyIII\Models\TransactionJournal');
}
);
// add fields
$this->fields[] = 'notes.text as notes';
$this->hasNotesInformation = true;
}
return $this;
}
/**
* @return GroupCollectorInterface
*/
public function withTagInformation(): GroupCollectorInterface
{
$this->fields[] = 'tags.id as tag_id';
$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->joinTagTables();
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', 'external_url');
$this->query->whereNotNull('journal_meta.data');
return $this;
}
@ -482,6 +652,27 @@ trait MetaCollection
return $this;
}
/**
* @inheritDoc
*/
public function withoutExternalUrl(): GroupCollectorInterface
{
$this->joinMetaDataTables();
// TODO not sure if this will work properly.
$this->query->where(function (Builder $q1) {
$q1->where(function (Builder $q2) {
$q2->where('journal_meta.name', '=', 'external_url');
$q2->whereNull('journal_meta.data');
})->orWhere(function (Builder $q3) {
$q3->where('journal_meta.name', '!=', 'external_url');
})->orWhere(function (Builder $q4) {
$q4->whereNull('journal_meta.name');
});
});
return $this;
}
/**
* @return GroupCollectorInterface
*/
@ -508,17 +699,4 @@ trait MetaCollection
return $this;
}
/**
* Join table to get tag information.
*/
protected function joinTagTables(): void
{
if (false === $this->hasJoinedTagTables) {
// join some extra tables:
$this->hasJoinedTagTables = true;
$this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id');
$this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id');
}
}
}

View File

@ -32,6 +32,383 @@ use FireflyIII\Helpers\Collector\GroupCollectorInterface;
*/
trait TimeCollection
{
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
return $this;
}
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
return $this;
}
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayAfter(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->day >= (int) $day;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @inheritDoc
*/
public function withMetaDate(string $field): GroupCollectorInterface
{
$this->joinMetaDataTables();
$this->query->where('journal_meta.name', '=', $field);
$this->query->whereNotNull('journal_meta.data');
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayBefore(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->day <= (int) $day;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayIs(string $day, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $day): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return (int) $day === $transaction[$field]->day;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthAfter(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->month >= (int) $month;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthBefore(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->month <= (int) $month;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthIs(string $month, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $month): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return (int) $month === $transaction[$field]->month;
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearAfter(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->year >= (int) $year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearBefore(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->year <= (int) $year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearIs(string $year, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $year): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $year === (string) $transaction[$field]->year;
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthBefore(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '<=', $month);
return $this;
}
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayAfter(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '>=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayBefore(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '<=', $day);
return $this;
}
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayIs(string $day, string $field): GroupCollectorInterface
{
$this->query->whereDay(sprintf('transaction_journals.%s', $field), '=', $day);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthAfter(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '>=', $month);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthBefore(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '<=', $month);
return $this;
}
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthIs(string $month, string $field): GroupCollectorInterface
{
$this->query->whereMonth(sprintf('transaction_journals.%s', $field), '=', $month);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearAfter(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '>=', $year);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearBefore(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '<=', $year);
return $this;
}
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearIs(string $year, string $field): GroupCollectorInterface
{
$this->query->whereYear(sprintf('transaction_journals.%s', $field), '=', $year);
return $this;
}
/**
* Collect transactions after a specific date.
@ -80,6 +457,123 @@ trait TimeCollection
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaAfter(Carbon $date, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$date->startOfDay();
$filter = function (int $index, array $object) use ($field, $date): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->gte($date);
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaBefore(Carbon $date, string $field): GroupCollectorInterface
{
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $date): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->lte($date);
}
}
return true;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaDateRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface
{
if ($end < $start) {
[$start, $end] = [$end, $start];
}
$end = clone $end; // this is so weird, but it works if $end and $start secretly point to the same object.
$end->endOfDay();
$start->startOfDay();
$this->withMetaDate($field);
$filter = function (int $index, array $object) use ($field, $start, $end): bool {
foreach ($object['transactions'] as $transaction) {
if (array_key_exists($field, $transaction) && $transaction[$field] instanceof Carbon
) {
return $transaction[$field]->gte($start) && $transaction[$field]->lte($end);
}
}
return false;
};
$this->postFilters[] = $filter;
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectAfter(Carbon $date, string $field): GroupCollectorInterface
{
$afterStr = $date->format('Y-m-d 00:00:00');
$this->query->where(sprintf('transaction_journals.%s', $field), '>=', $afterStr);
return $this;
}
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectBefore(Carbon $date, string $field): GroupCollectorInterface
{
die('a');
}
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface
{
$after = $start->format('Y-m-d 00:00:00');
$before = $end->format('Y-m-d 23:59:59');
$this->query->where(sprintf('transaction_journals.%s', $field), '>=', $after);
$this->query->where(sprintf('transaction_journals.%s', $field), '<=', $before);
return $this;
}
/**
* Set the start and end time of the results to return.
*
@ -120,22 +614,9 @@ trait TimeCollection
return $this;
}
public function yearIs(string $year): GroupCollectorInterface
public function yearAfter(string $year): GroupCollectorInterface
{
$this->query->whereYear('transaction_journals.date', '=', $year);
return $this;
}
public function monthIs(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '=', $month);
return $this;
}
public function dayIs(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '=', $day);
$this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
@ -145,35 +626,10 @@ trait TimeCollection
return $this;
}
public function monthBefore(string $month): GroupCollectorInterface
public function yearIs(string $year): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '<=', $month);
return $this;
}
public function dayBefore(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '<=', $day);
$this->query->whereYear('transaction_journals.date', '=', $year);
return $this;
}
public function yearAfter(string $year): GroupCollectorInterface
{
$this->query->whereYear('transaction_journals.date', '>=', $year);
return $this;
}
public function monthAfter(string $month): GroupCollectorInterface
{
$this->query->whereMonth('transaction_journals.date', '>=', $month);
return $this;
}
public function dayAfter(string $day): GroupCollectorInterface
{
$this->query->whereDay('transaction_journals.date', '>=', $day);
return $this;
}
}

View File

@ -25,9 +25,11 @@ namespace FireflyIII\Helpers\Collector;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use Closure;
use Exception;
use FireflyIII\Helpers\Collector\Extensions\AccountCollection;
use FireflyIII\Helpers\Collector\Extensions\AmountCollection;
use FireflyIII\Helpers\Collector\Extensions\AttachmentCollection;
use FireflyIII\Helpers\Collector\Extensions\CollectorProperties;
use FireflyIII\Helpers\Collector\Extensions\MetaCollection;
use FireflyIII\Helpers\Collector\Extensions\TimeCollection;
@ -48,13 +50,18 @@ use Log;
*/
class GroupCollector implements GroupCollectorInterface
{
use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection;
use CollectorProperties, AccountCollection, AmountCollection, TimeCollection, MetaCollection, AttachmentCollection;
/**
* Group collector constructor.
*/
public function __construct()
{
$this->postFilters = [];
$this->user = null;
$this->limit = null;
$this->page = null;
$this->hasAccountInfo = false;
$this->hasCatInformation = false;
$this->hasBudgetInformation = false;
@ -198,6 +205,26 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
*
*/
public function dumpQuery(): void
{
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
/**
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/**
* @inheritDoc
*/
@ -241,6 +268,11 @@ class GroupCollector implements GroupCollectorInterface
// now to parse this into an array.
$collection = $this->parseArray($result);
// filter the array using all available post filters:
$collection = $this->postFilterCollection($collection);
// count it and continue:
$this->total = $collection->count();
// now filter the array according to the page and the limit (if necessary)
@ -253,6 +285,285 @@ class GroupCollector implements GroupCollectorInterface
return $collection;
}
/**
* @param Collection $collection
*
* @return Collection
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionJournal $augumentedJournal */
foreach ($collection as $augumentedJournal) {
$groupId = $augumentedJournal->transaction_group_id;
if (!array_key_exists($groupId, $groups)) {
// make new array
$parsedGroup = $this->parseAugmentedJournal($augumentedJournal);
$groupArray = [
'id' => (int) $augumentedJournal->transaction_group_id,
'user_id' => (int) $augumentedJournal->user_id,
'title' => $augumentedJournal->transaction_group_title,
'transaction_type' => $parsedGroup['transaction_type_type'],
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int) $augumentedJournal->transaction_journal_id;
$groupArray['transactions'][$journalId] = $parsedGroup;
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int) $augumentedJournal->transaction_journal_id;
if (array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
$groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
$groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
}
if (!array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionJournal $augumentedJournal
*
* @return array
*/
private function parseAugmentedJournal(TransactionJournal $augumentedJournal): array
{
$result = $augumentedJournal->toArray();
$result['tags'] = [];
$result['attachments'] = [];
$result['interest_date'] = null;
$result['payment_date'] = null;
$result['invoice_date'] = null;
$result['book_date'] = null;
$result['due_date'] = null;
$result['process_date'] = null;
try {
$result['date'] = new Carbon($result['date'], 'UTC');
$result['created_at'] = new Carbon($result['created_at'], 'UTC');
$result['updated_at'] = new Carbon($result['updated_at'], 'UTC');
// this is going to happen a lot:
$result['date']->setTimezone(config('app.timezone'));
$result['created_at']->setTimezone(config('app.timezone'));
$result['updated_at']->setTimezone(config('app.timezone'));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
}
// try to process meta date value (if present)
$dates = ['interest_date', 'payment_date', 'invoice_date', 'book_date', 'due_date', 'process_date'];
if (array_key_exists('meta_name', $result) && in_array($result['meta_name'], $dates, true)) {
$name = $result['meta_name'];
if (array_key_exists('meta_data', $result) && '' !== (string) $result['meta_data']) {
$result[$name] = Carbon::createFromFormat('!Y-m-d', substr(json_decode($result['meta_data']), 0, 10));
}
}
// convert values to integers:
$result = $this->convertToInteger($result);
$result['reconciled'] = 1 === (int) $result['reconciled'];
if (array_key_exists('tag_id', $result) && null !== $result['tag_id']) { // assume the other fields are present as well.
$tagId = (int) $augumentedJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augumentedJournal['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int) $result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
];
}
// also merge attachments:
if (array_key_exists('attachment_id', $result)) {
$uploaded = 1 === (int) $result['attachment_uploaded'];
$attachmentId = (int) $augumentedJournal['attachment_id'];
if (0 !== $attachmentId && $uploaded) {
$result['attachments'][$attachmentId] = [
'id' => $attachmentId,
'filename' => $augumentedJournal['attachment_filename'],
'title' => $augumentedJournal['attachment_title'],
];
}
}
// unset various fields:
unset($result['tag_id'], $result['meta_data'], $result['meta_name'],
$result['tag_name'], $result['tag_date'], $result['tag_description'],
$result['tag_latitude'], $result['tag_longitude'], $result['tag_zoom_level'],
$result['attachment_filename'], $result['attachment_id']
);
return $result;
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int) $transaction['currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0');
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int) $transaction['foreign_currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd(
$groups[$groudId]['sums'][$currencyId]['amount'],
$transaction['foreign_amount'] ?? '0'
);
}
}
}
return $groups;
}
/**
* @param Collection $collection
* @return Collection
*/
private function postFilterCollection(Collection $collection): Collection
{
$currentCollection = $collection;
/**
* @var int $i
* @var Closure $function
*/
foreach ($this->postFilters as $function) {
$nextCollection = new Collection;
// loop everything in the current collection
// and save it (or not) in the new collection.
// that new collection is the next current collection
/**
* @var int $index
* @var array $item
*/
foreach ($currentCollection as $ii => $item) {
$result = $function($ii, $item);
if (false === $result) {
// skip other filters, continue to next item.
continue;
}
$nextCollection->push($item);
}
$currentCollection = $nextCollection;
}
return $currentCollection;
}
/**
* Same as getGroups but everything is in a paginator.
*
@ -269,19 +580,21 @@ class GroupCollector implements GroupCollectorInterface
}
/**
* Has attachments
* Limit the number of returned entries.
*
* @param int $limit
*
* @return GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface
public function setLimit(int $limit): GroupCollectorInterface
{
Log::debug('Add filter on attachment ID.');
$this->joinAttachmentTables();
$this->query->whereNotNull('attachments.attachable_id');
$this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
/**
* Limit results to a specific currency, either foreign or normal one.
*
@ -346,21 +659,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Limit the number of returned entries.
*
* @param int $limit
*
* @return GroupCollectorInterface
*/
public function setLimit(int $limit): GroupCollectorInterface
{
$this->limit = $limit;
app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit));
return $this;
}
/**
* Set the page to get.
*
@ -456,56 +754,6 @@ class GroupCollector implements GroupCollectorInterface
return $this;
}
/**
* Automatically include all stuff required to make API calls work.
*
* @return GroupCollectorInterface
*/
public function withAPIInformation(): GroupCollectorInterface
{
// include source + destination account name and type.
$this->withAccountInformation()
// include category ID + name (if any)
->withCategoryInformation()
// include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
return $this;
}
/**
* @inheritDoc
*/
public function withAttachmentInformation(): GroupCollectorInterface
{
$this->fields[] = 'attachments.id as attachment_id';
$this->fields[] = 'attachments.uploaded as attachment_uploaded';
$this->joinAttachmentTables();
return $this;
}
/**
* Join table to get attachment information.
*/
private function joinAttachmentTables(): void
{
if (false === $this->hasJoinedAttTables) {
// join some extra tables:
$this->hasJoinedAttTables = true;
$this->query->leftJoin('attachments', 'attachments.attachable_id', '=', 'transaction_journals.id')
->where(
static function (EloquentBuilder $q1) {
$q1->where('attachments.attachable_type', TransactionJournal::class);
//$q1->where('attachments.uploaded', true);
$q1->orWhereNull('attachments.attachable_type');
}
);
}
}
/**
* Build the query.
*/
@ -550,243 +798,21 @@ class GroupCollector implements GroupCollectorInterface
}
/**
* Automatically include all stuff required to make API calls work.
*
* @return GroupCollectorInterface
*/
public function dumpQuery(): void
public function withAPIInformation(): GroupCollectorInterface
{
echo $this->query->select($this->fields)->toSql();
echo '<pre>';
print_r($this->query->getBindings());
echo '</pre>';
}
// include source + destination account name and type.
$this->withAccountInformation()
// include category ID + name (if any)
->withCategoryInformation()
// include budget ID + name (if any)
->withBudgetInformation()
// include bill ID + name (if any)
->withBillInformation();
/**
*
*/
public function dumpQueryInLogs(): void
{
Log::debug($this->query->select($this->fields)->toSql());
Log::debug('Bindings', $this->query->getBindings());
}
/**
* Convert a selected set of fields to arrays.
*
* @param array $array
*
* @return array
*/
private function convertToInteger(array $array): array
{
foreach ($this->integerFields as $field) {
$array[$field] = array_key_exists($field, $array) ? (int) $array[$field] : null;
}
return $array;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeAttachments(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('attachment_id', $newArray)) {
$attachmentId = (int) $newJournal['attachment_id'];
$existingJournal['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
return $existingJournal;
}
/**
* @param array $existingJournal
* @param TransactionJournal $newJournal
*
* @return array
*/
private function mergeTags(array $existingJournal, TransactionJournal $newJournal): array
{
$newArray = $newJournal->toArray();
if (array_key_exists('tag_id', $newArray)) { // assume the other fields are present as well.
$tagId = (int) $newJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($newArray['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$existingJournal['tags'][$tagId] = [
'id' => (int) $newArray['tag_id'],
'name' => $newArray['tag_name'],
'date' => $tagDate,
'description' => $newArray['tag_description'],
];
}
return $existingJournal;
}
/**
* @param Collection $collection
*
* @return Collection
*/
private function parseArray(Collection $collection): Collection
{
$groups = [];
/** @var TransactionJournal $augumentedJournal */
foreach ($collection as $augumentedJournal) {
$groupId = $augumentedJournal->transaction_group_id;
if (!array_key_exists($groupId, $groups)) {
// make new array
$parsedGroup = $this->parseAugmentedJournal($augumentedJournal);
$groupArray = [
'id' => (int) $augumentedJournal->transaction_group_id,
'user_id' => (int) $augumentedJournal->user_id,
'title' => $augumentedJournal->transaction_group_title,
'transaction_type' => $parsedGroup['transaction_type_type'],
'count' => 1,
'sums' => [],
'transactions' => [],
];
$journalId = (int) $augumentedJournal->transaction_journal_id;
$groupArray['transactions'][$journalId] = $parsedGroup;
$groups[$groupId] = $groupArray;
continue;
}
// or parse the rest.
$journalId = (int) $augumentedJournal->transaction_journal_id;
if (array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// append data to existing group + journal (for multiple tags or multiple attachments)
$groups[$groupId]['transactions'][$journalId] = $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
$groups[$groupId]['transactions'][$journalId] = $this->mergeAttachments($groups[$groupId]['transactions'][$journalId], $augumentedJournal);
}
if (!array_key_exists($journalId, $groups[$groupId]['transactions'])) {
// create second, third, fourth split:
$groups[$groupId]['count']++;
$groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedJournal($augumentedJournal);
}
}
$groups = $this->parseSums($groups);
return new Collection($groups);
}
/**
* @param TransactionJournal $augumentedJournal
*
* @return array
*/
private function parseAugmentedJournal(TransactionJournal $augumentedJournal): array
{
$result = $augumentedJournal->toArray();
$result['tags'] = [];
$result['attachments'] = [];
try {
$result['date'] = new Carbon($result['date'], 'UTC');
$result['created_at'] = new Carbon($result['created_at'], 'UTC');
$result['updated_at'] = new Carbon($result['updated_at'], 'UTC');
// this is going to happen a lot:
$result['date']->setTimezone(config('app.timezone'));
$result['created_at']->setTimezone(config('app.timezone'));
$result['updated_at']->setTimezone(config('app.timezone'));
} catch (Exception $e) { // @phpstan-ignore-line
Log::error($e->getMessage());
}
// convert values to integers:
$result = $this->convertToInteger($result);
$result['reconciled'] = 1 === (int) $result['reconciled'];
if (array_key_exists('tag_id', $result) && null !== $result['tag_id']) { // assume the other fields are present as well.
$tagId = (int) $augumentedJournal['tag_id'];
$tagDate = null;
try {
$tagDate = Carbon::parse($augumentedJournal['tag_date']);
} catch (InvalidDateException $e) {
Log::debug(sprintf('Could not parse date: %s', $e->getMessage()));
}
$result['tags'][$tagId] = [
'id' => (int) $result['tag_id'],
'name' => $result['tag_name'],
'date' => $tagDate,
'description' => $result['tag_description'],
];
}
// also merge attachments:
if (array_key_exists('attachment_id', $result)) {
$uploaded = 1 === (int)$result['attachment_uploaded'];
$attachmentId = (int) $augumentedJournal['attachment_id'];
if (0 !== $attachmentId && $uploaded) {
$result['attachments'][$attachmentId] = [
'id' => $attachmentId,
];
}
}
return $result;
}
/**
* @param array $groups
*
* @return array
*/
private function parseSums(array $groups): array
{
/**
* @var int $groudId
* @var array $group
*/
foreach ($groups as $groudId => $group) {
/** @var array $transaction */
foreach ($group['transactions'] as $transaction) {
$currencyId = (int) $transaction['currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0');
if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) {
$currencyId = (int) $transaction['foreign_currency_id'];
// set default:
if (!array_key_exists($currencyId, $groups[$groudId]['sums'])) {
$groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId;
$groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code'];
$groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol'];
$groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places'];
$groups[$groudId]['sums'][$currencyId]['amount'] = '0';
}
$groups[$groudId]['sums'][$currencyId]['amount'] = bcadd(
$groups[$groudId]['sums'][$currencyId]['amount'],
$transaction['foreign_amount'] ?? '0'
);
}
}
}
return $groups;
return $this;
}
}

View File

@ -58,7 +58,7 @@ interface GroupCollectorInterface
public function amountLess(string $amount): GroupCollectorInterface;
/**
* Get transactions where the amount is more than.
* Get transactions where the foreign amount is more than.
*
* @param string $amount
*
@ -66,6 +66,72 @@ interface GroupCollectorInterface
*/
public function amountMore(string $amount): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameContains(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameEnds(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameIs(string $name): GroupCollectorInterface;
/**
* @param string $name
* @return GroupCollectorInterface
*/
public function attachmentNameStarts(string $name): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesAre(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesContains(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesEnds(string $value): GroupCollectorInterface;
/**
* @param string $value
* @return GroupCollectorInterface
*/
public function attachmentNotesStarts(string $value): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayAfter(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayBefore(string $day): GroupCollectorInterface;
/**
* @param string $day
* @return GroupCollectorInterface
*/
public function dayIs(string $day): GroupCollectorInterface;
/**
* End of the description must match:
*
@ -111,6 +177,42 @@ interface GroupCollectorInterface
*/
public function excludeSourceAccounts(Collection $accounts): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function externalIdStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlContains(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlEnds(string $url): GroupCollectorInterface;
/**
* @param string $url
* @return GroupCollectorInterface
*/
public function externalUrlStarts(string $url): GroupCollectorInterface;
/**
* Ensure the search will find nothing at all, zero results.
*
@ -118,6 +220,33 @@ interface GroupCollectorInterface
*/
public function findNothing(): GroupCollectorInterface;
/**
* Get transactions with a specific foreign amount.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountIs(string $amount): GroupCollectorInterface;
/**
* Get transactions where the amount is less than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountLess(string $amount): GroupCollectorInterface;
/**
* Get transactions where the foreign amount is more than.
*
* @param string $amount
*
* @return GroupCollectorInterface
*/
public function foreignAmountMore(string $amount): GroupCollectorInterface;
/**
* Return the transaction journals without group information. Is useful in some instances.
*
@ -151,6 +280,112 @@ interface GroupCollectorInterface
*/
public function hasAttachments(): GroupCollectorInterface;
/**
* Has no attachments
*
* @return GroupCollectorInterface
*/
public function hasNoAttachments(): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceContains(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceEnds(string $externalId): GroupCollectorInterface;
/**
* @param string $externalId
* @return GroupCollectorInterface
*/
public function internalReferenceStarts(string $externalId): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayAfter(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayBefore(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function metaDayIs(string $day, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthAfter(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthBefore(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function metaMonthIs(string $month, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearAfter(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearBefore(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function metaYearIs(string $year, string $field): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthAfter(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthBefore(string $month): GroupCollectorInterface;
/**
* @param string $month
* @return GroupCollectorInterface
*/
public function monthIs(string $month): GroupCollectorInterface;
/**
* @param string $value
*
@ -179,6 +414,69 @@ interface GroupCollectorInterface
*/
public function notesStartWith(string $value): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayAfter(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayBefore(string $day, string $field): GroupCollectorInterface;
/**
* @param string $day
* @param string $field
* @return GroupCollectorInterface
*/
public function objectDayIs(string $day, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthAfter(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthBefore(string $month, string $field): GroupCollectorInterface;
/**
* @param string $month
* @param string $field
* @return GroupCollectorInterface
*/
public function objectMonthIs(string $month, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearAfter(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearBefore(string $year, string $field): GroupCollectorInterface;
/**
* @param string $year
* @param string $field
* @return GroupCollectorInterface
*/
public function objectYearIs(string $year, string $field): GroupCollectorInterface;
/**
* Define which accounts can be part of the source and destination transactions.
*
@ -306,18 +604,10 @@ interface GroupCollectorInterface
public function setExternalId(string $externalId): GroupCollectorInterface;
/**
* Transactions without an external URL
*
* @param string $url
* @return GroupCollectorInterface
*/
public function withoutExternalUrl(): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
public function setExternalUrl(string $url): GroupCollectorInterface;
/**
* Limit results to a specific foreign currency.
@ -364,6 +654,57 @@ interface GroupCollectorInterface
*/
public function setLimit(int $limit): GroupCollectorInterface;
/**
* Collect transactions after a specific date.
*
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaAfter(Carbon $date, string $field): GroupCollectorInterface;
/**
* Collect transactions before a specific date.
*
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setMetaBefore(Carbon $date, string $field): GroupCollectorInterface;
/**
* Set the start and end time of the results to return, based on meta data.
*
* @param Carbon $start
* @param Carbon $end
* @param string $field
*
* @return GroupCollectorInterface
*/
public function setMetaDateRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface;
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectAfter(Carbon $date, string $field): GroupCollectorInterface;
/**
* @param Carbon $date
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectBefore(Carbon $date, string $field): GroupCollectorInterface;
/**
* @param Carbon $start
* @param Carbon $end
* @param string $field
* @return GroupCollectorInterface
*/
public function setObjectRange(Carbon $start, Carbon $end, string $field): GroupCollectorInterface;
/**
* Set the page to get.
*
@ -383,6 +724,15 @@ interface GroupCollectorInterface
*/
public function setRange(Carbon $start, Carbon $end): GroupCollectorInterface;
/**
* Look for specific recurring ID's.
*
* @param string $recurringId
*
* @return GroupCollectorInterface
*/
public function setRecurrenceId(string $recurringId): GroupCollectorInterface;
/**
* Search for words in descriptions.
*
@ -455,6 +805,15 @@ interface GroupCollectorInterface
*/
public function setUser(User $user): GroupCollectorInterface;
/**
* Only when does not have these tags
*
* @param Collection $tags
*
* @return GroupCollectorInterface
*/
public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface;
/**
* Either account can be set, but NOT both. This effectively excludes internal transfers.
*
@ -534,6 +893,21 @@ interface GroupCollectorInterface
*/
public function withCategoryInformation(): GroupCollectorInterface;
/**
* Transactions with an external URL
*
* @return GroupCollectorInterface
*/
public function withExternalUrl(): GroupCollectorInterface;
/**
* Transaction must have meta date field X.
*
* @param string $field
* @return GroupCollectorInterface
*/
public function withMetaDate(string $field): GroupCollectorInterface;
/**
* Will include notes.
*
@ -569,6 +943,13 @@ interface GroupCollectorInterface
*/
public function withoutCategory(): GroupCollectorInterface;
/**
* Transactions without an external URL
*
* @return GroupCollectorInterface
*/
public function withoutExternalUrl(): GroupCollectorInterface;
/**
* @return GroupCollectorInterface
*/
@ -579,15 +960,23 @@ interface GroupCollectorInterface
*/
public function withoutTags(): GroupCollectorInterface;
public function yearIs(string $year): GroupCollectorInterface;
public function monthIs(string $month): GroupCollectorInterface;
public function dayIs(string $day): GroupCollectorInterface;
public function yearBefore(string $year): GroupCollectorInterface;
public function monthBefore(string $month): GroupCollectorInterface;
public function dayBefore(string $day): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearAfter(string $year): GroupCollectorInterface;
public function monthAfter(string $month): GroupCollectorInterface;
public function dayAfter(string $day): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearBefore(string $year): GroupCollectorInterface;
/**
* @param string $year
* @return GroupCollectorInterface
*/
public function yearIs(string $year): GroupCollectorInterface;
}

View File

@ -46,6 +46,9 @@ class FiscalHelper implements FiscalHelperInterface
* @param Carbon $date
*
* @return Carbon date object
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function endOfFiscalYear(Carbon $date): Carbon
{
@ -69,6 +72,8 @@ class FiscalHelper implements FiscalHelperInterface
*
* @return Carbon date object
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function startOfFiscalYear(Carbon $date): Carbon
{

View File

@ -31,7 +31,6 @@ use FireflyIII\Support\CacheProperties;
use FireflyIII\User;
use Illuminate\Support\Collection;
use JsonException;
use Log;
/**
*
@ -64,6 +63,7 @@ class NetWorth implements NetWorthInterface
*
* @return array
* @throws JsonException
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function getNetWorthByCurrency(Collection $accounts, Carbon $date): array
{

View File

@ -141,7 +141,7 @@ class ReportHelper implements ReportHelperInterface
$currentEnd = clone $start;
$currentEnd->endOfMonth();
$months[$year]['months'][] = [
'formatted' => $start->formatLocalized('%B %Y'),
'formatted' => $start->isoFormat((string) trans('config.month_js')),
'start' => $start->format('Y-m-d'),
'end' => $currentEnd->format('Y-m-d'),
'month' => $start->month,

View File

@ -40,6 +40,8 @@ trait UpdateTrait
*
* @return array
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function getLatestRelease(): array
{

View File

@ -137,6 +137,8 @@ class CreateController extends Controller
*
* @return RedirectResponse|Redirector
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function store(AccountFormRequest $request)
{

View File

@ -74,6 +74,9 @@ class IndexController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function inactive(Request $request, string $objectType)
{
@ -128,7 +131,10 @@ class IndexController extends Controller
* @param string $objectType
*
* @return Factory|View
* @throws Exception
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index(Request $request, string $objectType)
{

View File

@ -86,7 +86,10 @@ class ReconcileController extends Controller
* @param Carbon|null $end
*
* @return Factory|RedirectResponse|Redirector|View
* @throws Exception
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
{
@ -237,7 +240,8 @@ class ReconcileController extends Controller
// title:
$description = trans(
'firefly.reconciliation_transaction_title',
['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)]
['from' => $start->isoFormat($this->monthAndDayFormat),
'to' => $end->isoFormat($this->monthAndDayFormat)]
);
$submission = [
'user' => auth()->user()->id,

View File

@ -83,8 +83,10 @@ class ShowController extends Controller
* @param Carbon|null $end
*
* @return RedirectResponse|Redirector|Factory|View
* @throws Exception
*
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
{
@ -109,8 +111,8 @@ class ShowController extends Controller
$page = (int) $request->get('page');
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency();
$fStart = $start->formatLocalized($this->monthAndDayFormat);
$fEnd = $end->formatLocalized($this->monthAndDayFormat);
$fStart = $start->isoFormat($this->monthAndDayFormat);
$fEnd = $end->isoFormat($this->monthAndDayFormat);
$subTitle = (string) trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]);
$chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$firstTransaction = $this->repository->oldestJournalDate($account) ?? $start;
@ -164,8 +166,10 @@ class ShowController extends Controller
* @param Account $account
*
* @return RedirectResponse|Redirector|Factory|View
* @throws Exception
*
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showAll(Request $request, Account $account)
{

View File

@ -61,6 +61,8 @@ class ConfigurationController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index()
{

View File

@ -55,6 +55,8 @@ class HomeController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index()
{
@ -83,9 +85,8 @@ class HomeController extends Controller
Log::channel('audit')->info('User sends test message.');
/** @var User $user */
$user = auth()->user();
$ipAddress = $request->ip();
Log::debug(sprintf('Now in testMessage() controller. IP is %s', $ipAddress));
event(new AdminRequestedTestMessage($user, $ipAddress));
Log::debug('Now in testMessage() controller.');
event(new AdminRequestedTestMessage($user));
session()->flash('info', (string) trans('firefly.send_test_triggered'));
return redirect(route('admin.index'));

View File

@ -61,6 +61,8 @@ class UpdateController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index()
{

View File

@ -110,6 +110,8 @@ class ForgotPasswordController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showLinkRequestForm()
{

View File

@ -22,7 +22,6 @@ declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Auth;
use Adldap;
use Cookie;
use DB;
use FireflyIII\Events\ActuallyLoggedIn;
@ -90,15 +89,6 @@ class LoginController extends Controller
Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get($this->username())));
Log::info('User is trying to login.');
$guard = config('auth.defaults.guard');
// if the user logs in using LDAP the field is also changed (per LDAP config)
if ('ldap' === $guard) {
Log::debug('User wishes to login using LDAP.');
$this->username = config('firefly.ldap_auth_field');
}
$this->validateLogin($request);
Log::debug('Login data is valid.');
@ -138,6 +128,37 @@ class LoginController extends Controller
$this->sendFailedLoginResponse($request);
}
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
return $this->username;
}
/**
* Get the failed login response instance.
*
* @param Request $request
*
* @return void
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$exception = ValidationException::withMessages(
[
$this->username() => [trans('auth.failed')],
]
);
$exception->redirectTo = route('login');
throw $exception;
}
/**
* Log the user out of the application.
*
@ -175,27 +196,6 @@ class LoginController extends Controller
: redirect('/');
}
/**
* Get the failed login response instance.
*
* @param Request $request
*
* @return void
*
* @throws ValidationException
*/
protected function sendFailedLoginResponse(Request $request)
{
$exception = ValidationException::withMessages(
[
$this->username() => [trans('auth.failed')],
]
);
$exception->redirectTo = route('login');
throw $exception;
}
/**
* Show the application's login form.
*
@ -203,6 +203,8 @@ class LoginController extends Controller
*
* @return Factory|Application|View|Redirector|RedirectResponse
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showLoginForm(Request $request)
{
@ -216,17 +218,6 @@ class LoginController extends Controller
return redirect(route('register'));
}
// switch to LDAP settings:
if ('ldap' === $guard) {
Log::debug('User wishes to login using LDAP.');
$this->username = config('firefly.ldap_auth_field');
}
// throw warning if still using login_provider
$ldapWarning = false;
if ('ldap' === config('firefly.login_provider')) {
$ldapWarning = true;
}
// is allowed to register, etc.
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$allowRegistration = true;
@ -251,16 +242,6 @@ class LoginController extends Controller
}
$usernameField = $this->username();
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'ldapWarning', 'allowReset', 'title', 'usernameField'));
}
/**
* Get the login username to be used by the controller.
*
* @return string
*/
public function username()
{
return $this->username;
return view('auth.login', compact('allowRegistration', 'email', 'remember', 'allowReset', 'title', 'usernameField'));
}
}

View File

@ -36,6 +36,8 @@ use Illuminate\Routing\Redirector;
use Illuminate\Validation\ValidationException;
use Illuminate\View\View;
use Log;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
/**
* Class RegisterController
@ -85,18 +87,7 @@ class RegisterController extends Controller
*/
public function register(Request $request)
{
// is allowed to?
$allowRegistration = true;
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$guard = config('auth.defaults.guard');
if (true === $singleUserMode && $userCount > 0 && 'ldap' !== $guard) {
$allowRegistration = false;
}
if ('ldap' === $guard) {
$allowRegistration = false;
}
$allowRegistration = $this->allowedToRegister();
if (false === $allowRegistration) {
throw new FireflyException('Registration is currently not available :(');
@ -105,7 +96,7 @@ class RegisterController extends Controller
$this->validator($request->all())->validate();
$user = $this->createUser($request->all());
Log::info(sprintf('Registered new user %s', $user->email));
event(new RegisteredUser($user, $request->ip()));
event(new RegisteredUser($user));
$this->guard()->login($user);
@ -116,34 +107,45 @@ class RegisterController extends Controller
return redirect($this->redirectPath());
}
/**
* @return bool
* @throws FireflyException
*/
protected function allowedToRegister(): bool
{
// is allowed to register?
$allowRegistration = true;
try {
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
$singleUserMode = true;
}
$userCount = User::count();
$guard = config('auth.defaults.guard');
if (true === $singleUserMode && $userCount > 0 && 'web' === $guard) {
$allowRegistration = false;
}
if ('web' !== $guard) {
$allowRegistration = false;
}
return $allowRegistration;
}
/**
* Show the application registration form.
*
* @param Request $request
*
* @return Factory|View
* @throws ContainerExceptionInterface
* @throws FireflyException
* @throws NotFoundExceptionInterface
*/
public function showRegistrationForm(Request $request)
{
$allowRegistration = true;
$isDemoSite = app('fireflyconfig')->get('is_demo_site', config('firefly.configuration.is_demo_site'))->data;
$singleUserMode = app('fireflyconfig')->get('single_user_mode', config('firefly.configuration.single_user_mode'))->data;
$userCount = User::count();
$pageTitle = (string) trans('firefly.register_page_title');
$guard = config('auth.defaults.guard');
if (true === $isDemoSite) {
$allowRegistration = false;
}
if (true === $singleUserMode && $userCount > 0 && 'ldap' !== $guard) {
$allowRegistration = false;
}
if ('ldap' === $guard) {
$allowRegistration = false;
}
$allowRegistration = $this->allowedToRegister();
if (false === $allowRegistration) {
$message = 'Registration is currently not available.';
@ -155,5 +157,4 @@ class RegisterController extends Controller
return view('auth.register', compact('isDemoSite', 'email', 'pageTitle'));
}
}

View File

@ -123,6 +123,8 @@ class ResetPasswordController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showResetForm(Request $request, $token = null)
{

View File

@ -102,6 +102,7 @@ class CreateController extends Controller
public function store(BillStoreRequest $request): RedirectResponse
{
$billData = $request->getBillData();
$billData['active'] = true;
try {
$bill = $this->repository->store($billData);

View File

@ -98,6 +98,8 @@ class EditController extends Controller
$hasOldInput = null !== $request->old('_token');
$preFilled = [
'bill_end_date' => $bill->end_date,
'extension_date' => $bill->extension_date,
'notes' => $this->repository->getNoteText($bill),
'transaction_currency_id' => $bill->transaction_currency_id,
'active' => $hasOldInput ? (bool) $request->old('active') : $bill->active,

View File

@ -136,8 +136,9 @@ class IndexController extends Controller
// summarise per currency / per group.
$sums = $this->getSums($bills);
$totals = $this->getTotals($sums);
$today = now()->startOfDay();
return view('bills.index', compact('bills', 'sums', 'total', 'totals'));
return view('bills.index', compact('bills', 'sums', 'total', 'totals', 'today'));
}
/**
@ -145,6 +146,8 @@ class IndexController extends Controller
*
* @return array
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
private function getSums(array $bills): array
{

View File

@ -123,7 +123,9 @@ class ShowController extends Controller
* @param Bill $bill
*
* @return Factory|View
* @throws FireflyException
* @throws \FireflyIII\Exceptions\FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function show(Request $request, Bill $bill)
{

View File

@ -171,6 +171,8 @@ class AvailableBudgetController extends Controller
* @param Request $request
*
* @return RedirectResponse|Redirector
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function store(Request $request)
{

View File

@ -92,6 +92,9 @@ class IndexController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index(Request $request, Carbon $start = null, Carbon $end = null)
{
@ -208,8 +211,8 @@ class IndexController extends Controller
$array['budgeted'][] = [
'id' => $limit->id,
'amount' => number_format((float) $limit->amount, $currency->decimal_places, '.', ''),
'start_date' => $limit->start_date->formatLocalized($this->monthAndDayFormat),
'end_date' => $limit->end_date->formatLocalized($this->monthAndDayFormat),
'start_date' => $limit->start_date->isoFormat($this->monthAndDayFormat),
'end_date' => $limit->end_date->isoFormat($this->monthAndDayFormat),
'in_range' => $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end),
'currency_id' => $currency->id,
'currency_symbol' => $currency->symbol,

View File

@ -79,6 +79,9 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function noBudget(Request $request, Carbon $start = null, Carbon $end = null)
{
@ -88,7 +91,7 @@ class ShowController extends Controller
$end = $end ?? session('end');
$subTitle = trans(
'firefly.without_budget_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
);
// get first journal ever to set off the budget period overview.
@ -115,6 +118,8 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function noBudgetAll(Request $request)
{
@ -144,6 +149,9 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function show(Request $request, Budget $budget)
{
@ -179,6 +187,9 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit)
{
@ -192,8 +203,8 @@ class ShowController extends Controller
'firefly.budget_in_period',
[
'name' => $budget->name,
'start' => $budgetLimit->start_date->formatLocalized($this->monthAndDayFormat),
'end' => $budgetLimit->end_date->formatLocalized($this->monthAndDayFormat),
'start' => $budgetLimit->start_date->isoFormat($this->monthAndDayFormat),
'end' => $budgetLimit->end_date->isoFormat($this->monthAndDayFormat),
'currency' => $budgetLimit->transactionCurrency->name,
]
);

View File

@ -68,6 +68,8 @@ class IndexController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function index(Request $request)
{

View File

@ -76,6 +76,9 @@ class NoCategoryController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function show(Request $request, Carbon $start = null, Carbon $end = null)
{
@ -88,7 +91,7 @@ class NoCategoryController extends Controller
$pageSize = (int) app('preferences')->get('listPageSize', 50)->data;
$subTitle = trans(
'firefly.without_category_between',
['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
['start' => $start->isoFormat($this->monthAndDayFormat), 'end' => $end->isoFormat($this->monthAndDayFormat)]
);
$periods = $this->getNoCategoryPeriodOverview($start);
@ -114,6 +117,8 @@ class NoCategoryController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showAll(Request $request)
{

View File

@ -78,6 +78,9 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null)
{
@ -94,8 +97,8 @@ class ShowController extends Controller
$path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]);
$subTitle = trans(
'firefly.journals_in_period_for_category',
['name' => $category->name, 'start' => $start->formatLocalized($this->monthAndDayFormat),
'end' => $end->formatLocalized($this->monthAndDayFormat),]
['name' => $category->name, 'start' => $start->isoFormat($this->monthAndDayFormat),
'end' => $end->isoFormat($this->monthAndDayFormat),]
);
/** @var GroupCollectorInterface $collector */
@ -118,6 +121,8 @@ class ShowController extends Controller
*
* @return Factory|View
* @throws FireflyException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function showAll(Request $request, Category $category)
{

View File

@ -50,14 +50,9 @@ class AccountController extends Controller
{
use DateCalculation, AugumentData, ChartGeneration;
/** @var GeneratorInterface Chart generation methods. */
protected $generator;
/** @var AccountRepositoryInterface Account repository. */
private $accountRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
protected GeneratorInterface $generator;
private AccountRepositoryInterface $accountRepository;
private CurrencyRepositoryInterface $currencyRepository;
/**
* AccountController constructor.
@ -180,6 +175,7 @@ class AccountController extends Controller
* @param Account $account
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{
@ -197,7 +193,6 @@ class AccountController extends Controller
* @param Carbon $end
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseBudget(Account $account, Carbon $start, Carbon $end): JsonResponse
{
@ -255,6 +250,7 @@ class AccountController extends Controller
* @param Account $account
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{
@ -272,7 +268,6 @@ class AccountController extends Controller
* @param Carbon $end
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
{
@ -328,6 +323,9 @@ class AccountController extends Controller
*
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
* @throws \Psr\Container\ContainerExceptionInterface
* @throws \Psr\Container\NotFoundExceptionInterface
*/
public function frontpage(AccountRepositoryInterface $repository): JsonResponse
{
@ -353,6 +351,7 @@ class AccountController extends Controller
* @param Account $account
*
* @return JsonResponse
* @throws JsonException
*/
public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse
{
@ -370,7 +369,6 @@ class AccountController extends Controller
* @param Carbon $end
*
* @return JsonResponse
* @throws JsonException
*/
public function incomeCategory(Account $account, Carbon $start, Carbon $end): JsonResponse
{
@ -440,7 +438,7 @@ class AccountController extends Controller
$cache->addProperty($end);
$cache->addProperty($account->id);
if ($cache->has()) {
return response()->json($cache->get());
//return response()->json($cache->get());
}
$currencies = $this->accountRepository->getUsedCurrencies($account);
@ -487,13 +485,13 @@ class AccountController extends Controller
break;
case '1D':
// per day the entire period, balance for every day.
$format = (string)trans('config.month_and_day', [], $locale);
$format = (string) trans('config.month_and_day_js', [], $locale);
$range = app('steam')->balanceInRange($account, $start, $end, $currency);
$previous = array_values($range)[0];
while ($end >= $current) {
$theDate = $current->format('Y-m-d');
$balance = $range[$theDate] ?? $previous;
$label = $current->formatLocalized($format);
$label = $current->isoFormat($format);
$entries[$label] = (float) $balance;
$previous = $balance;
$current->addDay();
@ -522,11 +520,13 @@ class AccountController extends Controller
*
* See reference nr. 55
*
* @param Carbon $start
* @param Carbon $end
* @param Collection $accounts
*
* @param Carbon $start
* @param Carbon $end
* @return JsonResponse
* @throws FireflyException
* @throws JsonException
*/
public function report(Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{

View File

@ -58,7 +58,6 @@ class BillController extends Controller
* @param BillRepositoryInterface $repository
*
* @return JsonResponse
* @throws JsonException
*/
public function frontpage(BillRepositoryInterface $repository): JsonResponse
{
@ -104,7 +103,7 @@ class BillController extends Controller
* @param Bill $bill
*
* @return JsonResponse
* @throws JsonException
* @throws \FireflyIII\Exceptions\FireflyException
*/
public function single(Bill $bill): JsonResponse
{
@ -145,7 +144,7 @@ class BillController extends Controller
];
foreach ($journals as $journal) {
$date = $journal['date']->formatLocalized((string)trans('config.month_and_day', [], $locale));
$date = $journal['date']->isoFormat((string) trans('config.month_and_day_js', [], $locale));
$chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill
$chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill

View File

@ -85,7 +85,6 @@ class BudgetController extends Controller
* @param Budget $budget
*
* @return JsonResponse
* @throws JsonException
*/
public function budget(Budget $budget): JsonResponse
{
@ -154,7 +153,6 @@ class BudgetController extends Controller
* @return JsonResponse
*
* @throws FireflyException
* @throws JsonException
*/
public function budgetLimit(Budget $budget, BudgetLimit $budgetLimit): JsonResponse
{
@ -184,7 +182,7 @@ class BudgetController extends Controller
$expenses = $this->opsRepository->sumExpenses($current, $current, null, $budgetCollection, $currency);
$spent = $expenses[(int) $currency->id]['sum'] ?? '0';
$amount = bcadd($amount, $spent);
$format = $start->formatLocalized((string)trans('config.month_and_day', [], $locale));
$format = $start->isoFormat((string) trans('config.month_and_day_js', [], $locale));
$entries[$format] = $amount;
$start->addDay();
@ -205,7 +203,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseAsset(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{
@ -274,7 +271,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseCategory(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{
@ -339,7 +335,6 @@ class BudgetController extends Controller
* @param BudgetLimit|null $budgetLimit
*
* @return JsonResponse
* @throws JsonException
*/
public function expenseExpense(Budget $budget, ?BudgetLimit $budgetLimit = null): JsonResponse
{
@ -403,7 +398,6 @@ class BudgetController extends Controller
* Shows a budget list with spent/left/overspent.
*
* @return JsonResponse
* @throws JsonException
*/
public function frontpage(): JsonResponse
{
@ -441,7 +435,6 @@ class BudgetController extends Controller
* @param Carbon $end
*
* @return JsonResponse
* @throws JsonException
*/
public function period(Budget $budget, TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{
@ -478,7 +471,7 @@ class BudgetController extends Controller
$currentStart = clone $start;
while ($currentStart <= $end) {
$currentStart = app('navigation')->startOfPeriod($currentStart, $preferredRange);
$title = $currentStart->formatLocalized($titleFormat);
$title = $currentStart->isoFormat($titleFormat);
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
// default limit is no limit:
@ -517,7 +510,6 @@ class BudgetController extends Controller
* @param Carbon $end
*
* @return JsonResponse
* @throws JsonException
*/
public function periodNoBudget(TransactionCurrency $currency, Collection $accounts, Carbon $start, Carbon $end): JsonResponse
{
@ -539,7 +531,7 @@ class BudgetController extends Controller
$preferredRange = app('navigation')->preferredRangeFormat($start, $end);
while ($currentStart <= $end) {
$currentEnd = app('navigation')->endOfPeriod($currentStart, $preferredRange);
$title = $currentStart->formatLocalized($titleFormat);
$title = $currentStart->isoFormat($titleFormat);
$sum = $this->nbRepository->sumExpenses($currentStart, $currentEnd, $accounts, $currency);
$amount = app('steam')->positive($sum[$currency->id]['sum'] ?? '0');
$chartData[$title] = round((float) $amount, $currency->decimal_places);

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