Expand API and refactor for user groups.

This commit is contained in:
James Cole
2023-09-21 15:50:49 +02:00
parent 7dbdf0c4ff
commit 0b220f3288
45 changed files with 950 additions and 243 deletions

View File

@@ -30,7 +30,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Support\Http\Api\AccountFilter;
use Illuminate\Http\JsonResponse;
@@ -79,6 +79,7 @@ class AccountController extends Controller
*/
public function accounts(AutocompleteRequest $request): JsonResponse
{
die('uses old administration ID check, needs to be updated. 1');
$data = $request->getData();
$types = $data['types'];
$query = $data['query'];

View File

@@ -31,7 +31,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use Illuminate\Http\JsonResponse;
use Psr\Container\ContainerExceptionInterface;
@@ -55,6 +55,7 @@ class AccountController extends Controller
$this->middleware(
function ($request, $next) {
$this->repository = app(AccountRepositoryInterface::class);
die('uses old administration ID check, needs to be updated.2');
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request);
}

View File

@@ -32,7 +32,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use Illuminate\Http\JsonResponse;

View File

@@ -32,8 +32,8 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -78,6 +78,7 @@ class BudgetController extends Controller
*/
public function dashboard(DateRequest $request): JsonResponse
{
die('uses old administration ID check, needs to be updated.3');
// get user.
/** @var User $user */
$user = auth()->user();

View File

@@ -32,7 +32,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\CleansChartData;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -53,6 +53,7 @@ class CategoryController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
die('uses old administration ID check, needs to be updated.4');
$this->accountRepos = app(AccountRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->accountRepos->setAdministrationId(auth()->user()->user_group_id);

View File

@@ -171,7 +171,7 @@ class Controller extends BaseController
*
* @return array
*/
final protected function jsonApiObject(string $key, Model $object, AbstractTransformer $transformer): array
final protected function jsonApiObject(string $key, array | Model $object, AbstractTransformer $transformer): array
{
// create some objects:
$manager = new Manager();

View File

@@ -27,7 +27,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Bill;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Models\Bill;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface;
use FireflyIII\Transformers\V2\AccountTransformer;
use FireflyIII\Transformers\V2\BillTransformer;
use Illuminate\Http\JsonResponse;
@@ -46,6 +46,7 @@ class ShowController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
die('uses old administration ID check, needs to be updated.5');
$this->repository = app(BillRepositoryInterface::class);
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request);

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Api\V2\Controllers\Model\Bill;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Generic\DateRequest;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface;
use Illuminate\Http\JsonResponse;
/**
@@ -63,6 +63,7 @@ class SumController extends Controller
*/
public function paid(DateRequest $request): JsonResponse
{
die('uses old administration ID check, needs to be updated.6');
$this->repository->setAdministrationId(auth()->user()->user_group_id);
$result = $this->repository->sumPaidInRange($this->parameters->get('start'), $this->parameters->get('end'));
@@ -82,6 +83,7 @@ class SumController extends Controller
*/
public function unpaid(DateRequest $request): JsonResponse
{
die('uses old administration ID check, needs to be updated.7');
$this->repository->setAdministrationId(auth()->user()->user_group_id);
$result = $this->repository->sumUnpaidInRange($this->parameters->get('start'), $this->parameters->get('end'));

View File

@@ -26,7 +26,7 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\PiggyBank;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Repositories\Administration\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\UserGroups\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Transformers\V2\PiggyBankTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
@@ -44,6 +44,7 @@ class ShowController extends Controller
parent::__construct();
$this->middleware(
function ($request, $next) {
die('uses old administration ID check, needs to be updated.8');
$this->repository = app(PiggyBankRepositoryInterface::class);
$this->repository->setAdministrationId(auth()->user()->user_group_id);
return $next($request);

View File

@@ -26,25 +26,105 @@ declare(strict_types=1);
namespace FireflyIII\Api\V2\Controllers\Model\Transaction;
use FireflyIII\Api\V2\Controllers\Controller;
use FireflyIII\Api\V2\Request\Model\Transaction\StoreRequest;
use FireflyIII\Events\StoredTransactionGroup;
use FireflyIII\Exceptions\DuplicateTransactionException;
use FireflyIII\Exceptions\FireflyException;
use Illuminate\Validation\ValidationException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface;
use FireflyIII\Rules\IsDuplicateTransaction;
use FireflyIII\Transformers\V2\TransactionGroupTransformer;
use FireflyIII\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Validator;
/**
* Class StoreController
*/
class StoreController extends Controller
{
private TransactionGroupRepositoryInterface $groupRepository;
/**
* TransactionController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
$this->groupRepository = app(TransactionGroupRepositoryInterface::class);
return $next($request);
}
);
}
/**
* TODO this method is practically the same as the V1 method and borrows as much code as possible.
* TODO still it duplicates a lot.
* TODO the v1 endpoints will never support separate administrations, this is an important distinction.
*
* @return JsonResponse
* @throws FireflyException
* @throws ValidationException
*/
public function post(): JsonResponse
public function post(StoreRequest $request): JsonResponse
{
return response()->json([]);
app('log')->debug('Now in API v2 StoreController::store()');
$data = $request->getAll();
$data['user'] = auth()->user()->id;
$userGroup = $request->getUserGroup();
$data['user_group'] = $userGroup;
// overrule user group and see where we end up.
// what happens when we refer to a budget that is not in this user group?
app('log')->channel('audit')->info('Store new transaction over API.', $data);
try {
$transactionGroup = $this->groupRepository->store($data);
} catch (DuplicateTransactionException $e) {
app('log')->warning('Caught a duplicate transaction. Return error message.');
$validator = Validator::make(
['transactions' => [['description' => $e->getMessage()]]],
['transactions.0.description' => new IsDuplicateTransaction()]
);
throw new ValidationException($validator, 0, $e);
} catch (FireflyException $e) {
app('log')->warning('Caught an exception. Return error message.');
app('log')->error($e->getMessage());
$message = sprintf('Internal exception: %s', $e->getMessage());
$validator = Validator::make(['transactions' => [['description' => $message]]], ['transactions.0.description' => new IsDuplicateTransaction()]);
throw new ValidationException($validator, 0, $e);
}
app('preferences')->mark();
$applyRules = $data['apply_rules'] ?? true;
$fireWebhooks = $data['fire_webhooks'] ?? true;
event(new StoredTransactionGroup($transactionGroup, $applyRules, $fireWebhooks));
/** @var User $admin */
$admin = auth()->user();
// use new group collector:
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector
->setUser($admin)
// filter on transaction group.
->setTransactionGroup($transactionGroup);
$selectedGroup = $collector->getGroups()->first();
if (null === $selectedGroup) {
throw new FireflyException('200032: Cannot find transaction. Possibly, a rule deleted this transaction after its creation.');
}
$transformer = new TransactionGroupTransformer();
$transformer->setParameters($this->parameters);
return response()
->api($this->jsonApiObject('transactions', $selectedGroup, $transformer))
->header('Content-Type', self::CONTENT_TYPE);
}

View File

@@ -36,11 +36,11 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\User;
@@ -77,6 +77,8 @@ class BasicController extends Controller
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
die('uses old administration ID check, needs to be updated.9');
$this->abRepository->setAdministrationId($user->user_group_id);
$this->accountRepository->setAdministrationId($user->user_group_id);
$this->billRepository->setAdministrationId($user->user_group_id);

View File

@@ -0,0 +1,317 @@
<?php
/*
* StoreRequest.php
* Copyright (c) 2023 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\Api\V2\Request\Model\Transaction;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\UserGroup;
use FireflyIII\Rules\BelongsUserGroup;
use FireflyIII\Rules\IsBoolean;
use FireflyIII\Rules\IsDateOrTime;
use FireflyIII\Support\NullArrayObject;
use FireflyIII\Support\Request\AppendsLocationData;
use FireflyIII\Support\Request\ChecksLogin;
use FireflyIII\Support\Request\ConvertsDataTypes;
use FireflyIII\User;
use FireflyIII\Validation\CurrencyValidation;
use FireflyIII\Validation\GroupValidation;
use FireflyIII\Validation\TransactionValidation;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Validator;
/**
* Class StoreRequest
*
* TODO this class is basically the same as the v1 request. However, it does not accept
* TODO models, objects and references that are NOT part of the designated user group (aka administration).
* TODO this distinction is already made in the CheckLogin trait, where there is also a convenient function
* TODO to grab the current UserGroup. This code is slightly different from other v2 apis that use
* TODO the "administration_id", those will have to be updated later on.
*/
class StoreRequest extends FormRequest
{
protected array $acceptedRoles = [UserRoleEnum::MANAGE_TRANSACTIONS];
use ChecksLogin;
use ConvertsDataTypes;
use TransactionValidation;
use GroupValidation;
use CurrencyValidation;
use AppendsLocationData;
/**
* Get all data.
*
* @return array
*/
public function getAll(): array
{
app('log')->debug('V2: Get all data in TransactionStoreRequest');
return [
'group_title' => $this->convertString('group_title'),
'error_if_duplicate_hash' => $this->boolean('error_if_duplicate_hash'),
'apply_rules' => $this->boolean('apply_rules', true),
'fire_webhooks' => $this->boolean('fire_webhooks', true),
'transactions' => $this->getTransactionData(),
];
// TODO include location and ability to process it.
}
/**
* Get transaction data.
*
* @return array
*/
private function getTransactionData(): array
{
$return = [];
/**
* @var array $transaction
*/
foreach ($this->get('transactions') as $transaction) {
$object = new NullArrayObject($transaction);
$return[] = [
'type' => $this->clearString($object['type'], false),
'date' => $this->dateFromValue($object['date']),
'order' => $this->integerFromValue((string)$object['order']),
'currency_id' => $this->integerFromValue((string)$object['currency_id']),
'currency_code' => $this->clearString((string)$object['currency_code'], false),
// foreign currency info:
'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']),
'foreign_currency_code' => $this->clearString((string)$object['foreign_currency_code'], false),
// amount and foreign amount. Cannot be 0.
'amount' => $this->clearString((string)$object['amount'], false),
'foreign_amount' => $this->clearString((string)$object['foreign_amount'], false),
// description.
'description' => $this->clearString($object['description'], false),
// source of transaction. If everything is null, assume cash account.
'source_id' => $this->integerFromValue((string)$object['source_id']),
'source_name' => $this->clearString((string)$object['source_name'], false),
'source_iban' => $this->clearString((string)$object['source_iban'], false),
'source_number' => $this->clearString((string)$object['source_number'], false),
'source_bic' => $this->clearString((string)$object['source_bic'], false),
// destination of transaction. If everything is null, assume cash account.
'destination_id' => $this->integerFromValue((string)$object['destination_id']),
'destination_name' => $this->clearString((string)$object['destination_name'], false),
'destination_iban' => $this->clearString((string)$object['destination_iban'], false),
'destination_number' => $this->clearString((string)$object['destination_number'], false),
'destination_bic' => $this->clearString((string)$object['destination_bic'], false),
// budget info
'budget_id' => $this->integerFromValue((string)$object['budget_id']),
'budget_name' => $this->clearString((string)$object['budget_name'], false),
// category info
'category_id' => $this->integerFromValue((string)$object['category_id']),
'category_name' => $this->clearString((string)$object['category_name'], false),
// journal bill reference. Optional. Will only work for withdrawals
'bill_id' => $this->integerFromValue((string)$object['bill_id']),
'bill_name' => $this->clearString((string)$object['bill_name'], false),
// piggy bank reference. Optional. Will only work for transfers
'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']),
'piggy_bank_name' => $this->clearString((string)$object['piggy_bank_name'], false),
// some other interesting properties
'reconciled' => $this->convertBoolean((string)$object['reconciled']),
'notes' => $this->clearString((string)$object['notes']),
'tags' => $this->arrayFromValue($object['tags']),
// all custom fields:
'internal_reference' => $this->clearString((string)$object['internal_reference'], false),
'external_id' => $this->clearString((string)$object['external_id'], false),
'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')),
'recurrence_id' => $this->integerFromValue($object['recurrence_id']),
'bunq_payment_id' => $this->clearString((string)$object['bunq_payment_id'], false),
'external_url' => $this->clearString((string)$object['external_url'], false),
'sepa_cc' => $this->clearString((string)$object['sepa_cc'], false),
'sepa_ct_op' => $this->clearString((string)$object['sepa_ct_op'], false),
'sepa_ct_id' => $this->clearString((string)$object['sepa_ct_id'], false),
'sepa_db' => $this->clearString((string)$object['sepa_db'], false),
'sepa_country' => $this->clearString((string)$object['sepa_country'], false),
'sepa_ep' => $this->clearString((string)$object['sepa_ep'], false),
'sepa_ci' => $this->clearString((string)$object['sepa_ci'], false),
'sepa_batch_id' => $this->clearString((string)$object['sepa_batch_id'], false),
// custom date fields. Must be Carbon objects. Presence is optional.
'interest_date' => $this->dateFromValue($object['interest_date']),
'book_date' => $this->dateFromValue($object['book_date']),
'process_date' => $this->dateFromValue($object['process_date']),
'due_date' => $this->dateFromValue($object['due_date']),
'payment_date' => $this->dateFromValue($object['payment_date']),
'invoice_date' => $this->dateFromValue($object['invoice_date']),
];
}
return $return;
}
/**
* The rules that the incoming request must be matched against.
*
* @return array
*/
public function rules(): array
{
app('log')->debug('V2: Collect rules of TransactionStoreRequest');
// at this point the userGroup can't be NULL because the
// authorize() method will complain. Loudly.
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
return [
// basic fields for group:
'group_title' => 'between:1,1000|nullable',
'error_if_duplicate_hash' => [new IsBoolean()],
'apply_rules' => [new IsBoolean()],
// transaction rules (in array for splits):
'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation',
'transactions.*.date' => ['required', new IsDateOrTime()],
'transactions.*.order' => 'numeric|min:0',
// currency info
'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id|nullable',
'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable',
// amount
'transactions.*.amount' => 'required|numeric|gt:0',
'transactions.*.foreign_amount' => 'numeric',
// description
'transactions.*.description' => 'nullable|between:1,1000',
// source of transaction
'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.source_name' => 'between:1,255|nullable',
'transactions.*.source_iban' => 'between:1,255|nullable|iban',
'transactions.*.source_number' => 'between:1,255|nullable',
'transactions.*.source_bic' => 'between:1,255|nullable|bic',
// destination of transaction
'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.destination_name' => 'between:1,255|nullable',
'transactions.*.destination_iban' => 'between:1,255|nullable|iban',
'transactions.*.destination_number' => 'between:1,255|nullable',
'transactions.*.destination_bic' => 'between:1,255|nullable|bic',
// budget, category, bill and piggy
'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUserGroup($userGroup)],
'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUserGroup($userGroup), 'nullable'],
'transactions.*.category_name' => 'between:1,255|nullable',
'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUserGroup($userGroup)],
'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUserGroup($userGroup)],
'transactions.*.piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUserGroup($userGroup)],
// other interesting fields
'transactions.*.reconciled' => [new IsBoolean()],
'transactions.*.notes' => 'min:1|max:50000|nullable',
'transactions.*.tags' => 'between:0,255',
// meta info fields
'transactions.*.internal_reference' => 'min:1|max:255|nullable',
'transactions.*.external_id' => 'min:1|max:255|nullable',
'transactions.*.recurrence_id' => 'min:1|max:255|nullable',
'transactions.*.bunq_payment_id' => 'min:1|max:255|nullable',
'transactions.*.external_url' => 'min:1|max:255|nullable|url',
// SEPA fields:
'transactions.*.sepa_cc' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_op' => 'min:1|max:255|nullable',
'transactions.*.sepa_ct_id' => 'min:1|max:255|nullable',
'transactions.*.sepa_db' => 'min:1|max:255|nullable',
'transactions.*.sepa_country' => 'min:1|max:255|nullable',
'transactions.*.sepa_ep' => 'min:1|max:255|nullable',
'transactions.*.sepa_ci' => 'min:1|max:255|nullable',
'transactions.*.sepa_batch_id' => 'min:1|max:255|nullable',
// dates
'transactions.*.interest_date' => 'date|nullable',
'transactions.*.book_date' => 'date|nullable',
'transactions.*.process_date' => 'date|nullable',
'transactions.*.due_date' => 'date|nullable',
'transactions.*.payment_date' => 'date|nullable',
'transactions.*.invoice_date' => 'date|nullable',
];
}
/**
* Configure the validator instance.
*
* @param Validator $validator
*
* @return void
*/
public function withValidator(Validator $validator): void
{
/** @var User $user */
$user = auth()->user();
/** @var UserGroup $userGroup */
$userGroup = $this->getUserGroup();
$validator->after(
function (Validator $validator) use ($user, $userGroup) {
// must be valid array.
$this->validateTransactionArray($validator); // does not need group validation.
// must submit at least one transaction.
app('log')->debug('Now going to validateOneTransaction');
$this->validateOneTransaction($validator); // does not need group validation.
app('log')->debug('Now done with validateOneTransaction');
// all journals must have a description
$this->validateDescriptions($validator); // does not need group validation.
// all transaction types must be equal:
$this->validateTransactionTypes($validator); // does not need group validation.
// validate foreign currency info
$this->validateForeignCurrencyInformation($validator); // does not need group validation.
// validate all account info
$this->validateAccountInformation($validator, $user, $userGroup);
// validate source/destination is equal, depending on the transaction journal type.
$this->validateEqualAccounts($validator);
// the group must have a description if > 1 journal.
$this->validateGroupDescription($validator);
}
);
}
}

View File

@@ -81,7 +81,7 @@ class TransactionGroupFactory
$group = new TransactionGroup();
$group->user()->associate($this->user);
$group->userGroup()->associate($this->user->userGroup);
$group->userGroup()->associate($data['user_group'] ?? $this->user->userGroup);
$group->title = $title;
$group->save();

View File

@@ -33,7 +33,7 @@ class TransactionObserver
public function deleting(Transaction $transaction): void
{
app('log')->debug('Observe "deleting" of a transaction.');
$transaction->transactionJournal->delete();
$transaction?->transactionJournal?->delete();
}
}

View File

@@ -29,7 +29,7 @@ use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
@@ -245,6 +245,7 @@ class NetWorth implements NetWorthInterface
{
$this->userGroup = $userGroup;
$this->adminAccountRepository = app(AdminAccountRepositoryInterface::class);
die('uses old administration ID check, needs to be updated.A');
$this->adminAccountRepository->setAdministrationId($userGroup->id);
}

View File

@@ -29,8 +29,8 @@ use FireflyIII\Repositories\Account\AccountTasker;
use FireflyIII\Repositories\Account\AccountTaskerInterface;
use FireflyIII\Repositories\Account\OperationsRepository;
use FireflyIII\Repositories\Account\OperationsRepositoryInterface;
use FireflyIII\Repositories\Administration\Account\AccountRepository as AdminAccountRepository;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepository as AdminAccountRepository;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as AdminAccountRepositoryInterface;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -42,9 +42,7 @@ class AccountServiceProvider extends ServiceProvider
/**
* Bootstrap the application services.
*/
public function boot(): void
{
}
public function boot(): void {}
/**
* Register the application services.
@@ -84,7 +82,6 @@ class AccountServiceProvider extends ServiceProvider
// phpstan thinks auth does not exist.
if ($app->auth->check()) { // @phpstan-ignore-line
$repository->setUser(auth()->user());
$repository->setAdministrationId((int)auth()->user()->user_group_id);
}
return $repository;

View File

@@ -25,8 +25,8 @@ namespace FireflyIII\Providers;
use FireflyIII\Repositories\Bill\BillRepository;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Administration\Bill\BillRepository as AdminBillRepository;
use FireflyIII\Repositories\Administration\Bill\BillRepositoryInterface as AdminBillRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Bill\BillRepository as AdminBillRepository;
use FireflyIII\Repositories\UserGroups\Bill\BillRepositoryInterface as AdminBillRepositoryInterface;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -38,9 +38,7 @@ class BillServiceProvider extends ServiceProvider
/**
* Bootstrap the application services.
*/
public function boot(): void
{
}
public function boot(): void {}
/**
* Register the application services.

View File

@@ -25,20 +25,20 @@ namespace FireflyIII\Providers;
use FireflyIII\Repositories\Budget\AvailableBudgetRepository;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\AvailableBudgetRepository as AdminAbRepository;
use FireflyIII\Repositories\Administration\Budget\AvailableBudgetRepositoryInterface as AdminAbRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\AvailableBudgetRepository as AdminAbRepository;
use FireflyIII\Repositories\UserGroups\Budget\AvailableBudgetRepositoryInterface as AdminAbRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepository;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepository;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\BudgetRepository as AdminBudgetRepository;
use FireflyIII\Repositories\Administration\Budget\BudgetRepositoryInterface as AdminBudgetRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\BudgetRepository as AdminBudgetRepository;
use FireflyIII\Repositories\UserGroups\Budget\BudgetRepositoryInterface as AdminBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\NoBudgetRepository;
use FireflyIII\Repositories\Budget\NoBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepository;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Administration\Budget\OperationsRepository as AdminOperationsRepository;
use FireflyIII\Repositories\Administration\Budget\OperationsRepositoryInterface as AdminOperationsRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Budget\OperationsRepository as AdminOperationsRepository;
use FireflyIII\Repositories\UserGroups\Budget\OperationsRepositoryInterface as AdminOperationsRepositoryInterface;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -50,9 +50,7 @@ class BudgetServiceProvider extends ServiceProvider
/**
* Bootstrap the application services.
*/
public function boot(): void
{
}
public function boot(): void {}
/**
* Register the application services.
@@ -80,6 +78,7 @@ class BudgetServiceProvider extends ServiceProvider
$repository = app(AdminBudgetRepository::class);
if ($app->auth->check()) { // @phpstan-ignore-line
$repository->setUser(auth()->user());
die('uses old administration ID check, needs to be updated.C');
$repository->setAdministrationId(auth()->user()->user_group_id);
}
@@ -109,6 +108,7 @@ class BudgetServiceProvider extends ServiceProvider
$repository = app(AdminAbRepository::class);
if ($app->auth->check()) { // @phpstan-ignore-line
$repository->setUser(auth()->user());
die('uses old administration ID check, needs to be updated.D');
$repository->setAdministrationId(auth()->user()->user_group_id);
}
@@ -164,6 +164,7 @@ class BudgetServiceProvider extends ServiceProvider
$repository = app(AdminOperationsRepository::class);
if ($app->auth->check()) { // @phpstan-ignore-line
$repository->setUser(auth()->user());
die('uses old administration ID check, needs to be updated.E');
$repository->setAdministrationId(auth()->user()->user_group_id);
}

View File

@@ -26,8 +26,8 @@ namespace FireflyIII\Providers;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepository;
use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface;
use FireflyIII\Repositories\Administration\PiggyBank\PiggyBankRepository as AdminPiggyBankRepository;
use FireflyIII\Repositories\Administration\PiggyBank\PiggyBankRepositoryInterface as AdminPiggyBankRepositoryInterface;
use FireflyIII\Repositories\UserGroups\PiggyBank\PiggyBankRepository as AdminPiggyBankRepository;
use FireflyIII\Repositories\UserGroups\PiggyBank\PiggyBankRepositoryInterface as AdminPiggyBankRepositoryInterface;
use Illuminate\Foundation\Application;
use Illuminate\Support\ServiceProvider;
@@ -40,9 +40,7 @@ class PiggyBankServiceProvider extends ServiceProvider
/**
* Bootstrap the application services.
*/
public function boot(): void
{
}
public function boot(): void {}
/**
* Register the application services.

View File

@@ -23,13 +23,13 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Account;
namespace FireflyIII\Repositories\UserGroups\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Support\Collection;
@@ -38,7 +38,7 @@ use Illuminate\Support\Collection;
*/
class AccountRepository implements AccountRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* @param Account $account

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Account;
namespace FireflyIII\Repositories\UserGroups\Account;
use FireflyIII\Models\Account;
use FireflyIII\Models\TransactionCurrency;

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Bill;
namespace FireflyIII\Repositories\UserGroups\Bill;
use Carbon\Carbon;
use FireflyIII\Models\Bill;
@@ -31,7 +31,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Support\CacheProperties;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
/**
@@ -39,7 +39,7 @@ use Illuminate\Support\Collection;
*/
class BillRepository implements BillRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* Correct order of piggies in case of issues.

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Bill;
namespace FireflyIII\Repositories\UserGroups\Bill;
use Carbon\Carbon;
use FireflyIII\Models\Bill;

View File

@@ -23,19 +23,19 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use Carbon\Carbon;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Support\Http\Api\ExchangeRateConverter;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
/**
* Class AvailableBudgetRepository
*/
class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* @param Carbon $start

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use Carbon\Carbon;

View File

@@ -23,9 +23,9 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
/**
@@ -33,7 +33,7 @@ use Illuminate\Support\Collection;
*/
class BudgetRepository implements BudgetRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* @inheritDoc

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use Illuminate\Support\Collection;

View File

@@ -23,13 +23,13 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
use FireflyIII\Models\TransactionType;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
/**
@@ -37,7 +37,7 @@ use Illuminate\Support\Collection;
*/
class OperationsRepository implements OperationsRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* @inheritDoc
@@ -132,6 +132,7 @@ class OperationsRepository implements OperationsRepositoryInterface
{
/** @var BudgetRepositoryInterface $repos */
$repos = app(BudgetRepositoryInterface::class);
die('uses old administration ID check, needs to be updated.F');
$repos->setAdministrationId($this->getAdministrationId());
return $repos->getActiveBudgets();

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\Budget;
namespace FireflyIII\Repositories\UserGroups\Budget;
use Carbon\Carbon;
use Illuminate\Support\Collection;

View File

@@ -23,9 +23,9 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\PiggyBank;
namespace FireflyIII\Repositories\UserGroups\PiggyBank;
use FireflyIII\Support\Repositories\Administration\AdministrationTrait;
use FireflyIII\Support\Repositories\UserGroup\UserGroupTrait;
use Illuminate\Support\Collection;
/**
@@ -33,7 +33,7 @@ use Illuminate\Support\Collection;
*/
class PiggyBankRepository implements PiggyBankRepositoryInterface
{
use AdministrationTrait;
use UserGroupTrait;
/**
* @inheritDoc

View File

@@ -23,7 +23,7 @@
declare(strict_types=1);
namespace FireflyIII\Repositories\Administration\PiggyBank;
namespace FireflyIII\Repositories\UserGroups\PiggyBank;
use Illuminate\Support\Collection;

View File

@@ -24,6 +24,7 @@ declare(strict_types=1);
namespace FireflyIII\Rules;
use Closure;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
@@ -31,54 +32,28 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionJournal;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
/**
* Class BelongsUser
*/
class BelongsUser implements Rule
class BelongsUser implements ValidationRule
{
/**
* Create a new rule instance.
*
* @return void
* @inheritDoc
*/
public function __construct()
{
//
}
/**
* Get the validation error message.
*
* @return string
*/
public function message(): string
{
return (string)trans('validation.belongs_user');
}
/**
* Determine if the validation rule passes.
*
* @param string $attribute
* @param mixed $value
*
* @return bool
* @throws FireflyException
*
*/
public function passes($attribute, $value): bool
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$attribute = $this->parseAttribute($attribute);
if (!auth()->check()) {
return true;
$fail('validation.belongs_user')->translate();
return;
}
$attribute = (string)$attribute;
Log::debug(sprintf('Going to validate %s', $attribute));
return match ($attribute) {
$result = match ($attribute) {
'piggy_bank_id' => $this->validatePiggyBankId((int)$value),
'piggy_bank_name' => $this->validatePiggyBankName($value),
'bill_id' => $this->validateBillId((int)$value),
@@ -88,8 +63,11 @@ class BelongsUser implements Rule
'category_id' => $this->validateCategoryId((int)$value),
'budget_name' => $this->validateBudgetName($value),
'source_id', 'destination_id' => $this->validateAccountId((int)$value),
default => throw new FireflyException(sprintf('Rule BelongUser cannot handle "%s"', $attribute)),
default => throw new FireflyException(sprintf('Rule BelongsUser cannot handle "%s"', $attribute)),
};
if (false === $result) {
$fail('validation.belongs_user')->translate();
}
}
/**

View File

@@ -0,0 +1,262 @@
<?php
/*
* BelongsUserGroup.php
* Copyright (c) 2023 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\Rules;
use Closure;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\Bill;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
use FireflyIII\Models\PiggyBank;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\UserGroup;
use Illuminate\Contracts\Validation\ValidationRule;
use Illuminate\Support\Facades\Log;
/**
* Class BelongsUserGroup
* TODO this method has a lot in common with BelongsUser but will check if the UserGroup
* TODO that is submitted is valid. This method will not validate if the user has a valid ROLE in this
* TODO group.
*/
class BelongsUserGroup implements ValidationRule
{
private UserGroup $userGroup;
/**
* Create a new rule instance.
*
* @return void
*/
public function __construct(UserGroup $userGroup)
{
$this->userGroup = $userGroup;
}
/**
* @inheritDoc
*/
public function validate(string $attribute, mixed $value, Closure $fail): void
{
$attribute = $this->parseAttribute($attribute);
if (!auth()->check()) {
$fail('validation.belongs_user')->translate();
return;
}
$attribute = (string)$attribute;
Log::debug(sprintf('Going to validate %s', $attribute));
$result = match ($attribute) {
'piggy_bank_id' => $this->validatePiggyBankId((int)$value),
'piggy_bank_name' => $this->validatePiggyBankName($value),
'bill_id' => $this->validateBillId((int)$value),
'transaction_journal_id' => $this->validateJournalId((int)$value),
'bill_name' => $this->validateBillName($value),
'budget_id' => $this->validateBudgetId((int)$value),
'category_id' => $this->validateCategoryId((int)$value),
'budget_name' => $this->validateBudgetName($value),
'source_id', 'destination_id' => $this->validateAccountId((int)$value),
default => throw new FireflyException(sprintf('Rule BelongsUser cannot handle "%s"', $attribute)),
};
if (false === $result) {
$fail('validation.belongs_user_or_user_group')->translate();
}
}
/**
* @param string $attribute
*
* @return string
*/
private function parseAttribute(string $attribute): string
{
$parts = explode('.', $attribute);
if (1 === count($parts)) {
return $attribute;
}
if (3 === count($parts)) {
return $parts[2];
}
return $attribute;
}
/**
* @param int $value
*
* @return bool
*/
private function validatePiggyBankId(int $value): bool
{
$count = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
->where('piggy_banks.id', '=', $value)
->where('accounts.user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
/**
* @param string $value
*
* @return bool
*/
private function validatePiggyBankName(string $value): bool
{
$count = $this->countField(PiggyBank::class, 'name', $value);
return 1 === $count;
}
/**
* @param string $class
* @param string $field
* @param string $value
*
* @return int
*
*/
protected function countField(string $class, string $field, string $value): int
{
$value = trim($value);
$objects = [];
// get all objects belonging to user:
if (PiggyBank::class === $class) {
$objects = PiggyBank::leftJoin('accounts', 'accounts.id', '=', 'piggy_banks.account_id')
->where('accounts.user_group_id', '=', $this->userGroup->id)->get(['piggy_banks.*']);
}
if (PiggyBank::class !== $class) {
$objects = $class::where('user_group_id', '=', $this->userGroup->id)->get();
}
$count = 0;
foreach ($objects as $object) {
$objectValue = trim((string)$object->$field);
app('log')->debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value));
if ($objectValue === $value) {
$count++;
app('log')->debug(sprintf('Hit! Count is now %d', $count));
}
}
return $count;
}
/**
* @param int $value
*
* @return bool
*/
private function validateBillId(int $value): bool
{
if (0 === $value) {
return true;
}
$count = Bill::where('id', '=', $value)->where('user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
/**
* @param int $value
*
* @return bool
*/
private function validateJournalId(int $value): bool
{
if (0 === $value) {
return true;
}
$count = TransactionJournal::where('id', '=', $value)->where('user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
/**
* @param string $value
*
* @return bool
*/
private function validateBillName(string $value): bool
{
$count = $this->countField(Bill::class, 'name', $value);
app('log')->debug(sprintf('Result of countField for bill name "%s" is %d', $value, $count));
return 1 === $count;
}
/**
* @param int $value
*
* @return bool
*/
private function validateBudgetId(int $value): bool
{
if (0 === $value) {
return true;
}
$count = Budget::where('id', '=', $value)->where('user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
/**
* @param int $value
*
* @return bool
*/
private function validateCategoryId(int $value): bool
{
$count = Category::where('id', '=', $value)->where('user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
/**
* @param string $value
*
* @return bool
*/
private function validateBudgetName(string $value): bool
{
$count = $this->countField(Budget::class, 'name', $value);
return 1 === $count;
}
/**
* @param int $value
*
* @return bool
*/
private function validateAccountId(int $value): bool
{
if (0 === $value) {
// its ok to submit 0. other checks will fail.
return true;
}
$count = Account::where('id', '=', $value)->where('user_group_id', '=', $this->userGroup->id)->count();
return 1 === $count;
}
}

View File

@@ -1,95 +0,0 @@
<?php
/*
* AdministrationTrait.php
* Copyright (c) 2023 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\Support\Repositories\Administration;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
/**
* Trait AdministrationTrait
*/
trait AdministrationTrait
{
protected ?int $administrationId = null;
protected User $user;
protected ?UserGroup $userGroup = null;
/**
* @return int
*/
public function getAdministrationId(): int
{
return $this->administrationId;
}
/**
* @param int $administrationId
*
* @throws FireflyException
*/
public function setAdministrationId(int $administrationId): void
{
$this->administrationId = $administrationId;
$this->refreshAdministration();
}
/**
* @return void
* @throws FireflyException
*/
private function refreshAdministration(): void
{
if (null !== $this->administrationId) {
$memberships = GroupMembership::where('user_id', $this->user->id)
->where('user_group_id', $this->administrationId)
->count();
if (0 === $memberships) {
throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $this->administrationId));
}
$this->userGroup = UserGroup::find($this->administrationId);
if (null === $this->userGroup) {
throw new FireflyException(sprintf('Unfound administration for user #%d', $this->user->id));
}
return;
}
throw new FireflyException(sprintf('Cannot validate administration for user #%d', $this->user->id));
}
/**
* @param Authenticatable|User|null $user
*
* @return void
*/
public function setUser(Authenticatable|User|null $user): void
{
if (null !== $user) {
$this->user = $user;
}
}
}

View File

@@ -0,0 +1,85 @@
<?php
/*
* UserGroupTrait.php
* Copyright (c) 2023 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\Support\Repositories\UserGroup;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\GroupMembership;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Contracts\Auth\Authenticatable;
/**
* Trait UserGroupTrait
*/
trait UserGroupTrait
{
protected User $user;
protected UserGroup $userGroup;
/**
* @param Authenticatable|User|null $user
*
* @return void
*/
public function setUser(Authenticatable | User | null $user): void
{
if (null !== $user) {
$this->user = $user;
$this->userGroup = $user->userGroup;
}
}
/**
* TODO This method does not check if the user has access to this particular user group.
*
* @param UserGroup $userGroup
*
* @return void
*/
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
}
/**
* @param int $userGroupId
*
* @throws FireflyException
*/
public function setUserGroupById(int $userGroupId): void
{
$memberships = GroupMembership::where('user_id', $this->user->id)
->where('user_group_id', $userGroupId)
->count();
if (0 === $memberships) {
throw new FireflyException(sprintf('User #%d has no access to administration #%d', $this->user->id, $userGroupId));
}
$this->userGroup = UserGroup::find($userGroupId);
if (null === $this->userGroup) {
throw new FireflyException(sprintf('Unfound administration for user #%d', $this->user->id));
}
}
}

View File

@@ -27,7 +27,6 @@ use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Models\UserGroup;
use FireflyIII\User;
use Illuminate\Support\Facades\Log;
use ValueError;
/**
* Trait ChecksLogin
@@ -51,20 +50,51 @@ trait ChecksLogin
app('log')->debug('Request class has no acceptedRoles array');
return true; // check for false already took place.
}
/** @var UserGroup $userGroup */
$userGroup = $this->route()->parameter('userGroup');
if (null === $userGroup) {
app('log')->debug('Request class has no userGroup parameter.');
return true;
}
/** @var User $user */
$user = auth()->user();
$user = auth()->user();
$userGroup = $this->getUserGroup();
if (null === $userGroup) {
app('log')->error('User has no valid user group submitted or otherwise.');
return false;
}
/** @var UserRoleEnum $role */
foreach ($this->acceptedRoles as $role) {
if ($user->hasRoleInGroup($userGroup, $role, true, true)) {
// system owner cannot overrule this, MUST be member of the group.
if ($user->hasRoleInGroup($userGroup, $role, true, false)) {
return true;
}
}
return false;
}
/**
* Return the user group or NULL if none is set.
* Will throw exception if invalid.
*
* @return UserGroup|null
*/
public function getUserGroup(): ?UserGroup
{
/** @var User $user */
$user = auth()->user();
app('log')->debug('Now in getUserGroup()');
/** @var UserGroup $userGroup */
$userGroup = $this->route()->parameter('userGroup');
if (null === $userGroup) {
app('log')->debug('Request class has no userGroup parameter, but perhaps there is a parameter.');
$userGroupId = (int)$this->get('user_group_id');
if (0 === $userGroupId) {
app('log')->debug(sprintf('Request class has no user_group_id parameter, grab default from user (group #%d).', $user->user_group_id));
$userGroupId = (int)$user->user_group_id;
}
$userGroup = UserGroup::find($userGroupId);
if (null === $userGroup) {
app('log')->error(sprintf('Request class has user_group_id (#%d), but group does not exist.', $userGroupId));
return null;
}
app('log')->debug('Request class has valid user_group_id.');
}
return $userGroup;
}
}

View File

@@ -26,7 +26,7 @@ namespace FireflyIII\Support\Request;
use Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use Carbon\Exceptions\InvalidFormatException;
use FireflyIII\Repositories\Administration\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
@@ -171,6 +171,7 @@ trait ConvertsDataTypes
// set administration ID
// group ID
$administrationId = auth()->user()->getAdministrationId();
die('uses old administration ID check, needs to be updated.G');
$repository->setAdministrationId($administrationId);
$set = $this->get('accounts');

View File

@@ -139,7 +139,7 @@ trait DepositValidation
// if the user submits an ID, but that ID is not of the correct type,
// return false.
if (null !== $accountId) {
$search = $this->accountRepository->find($accountId);
$search = $this->getRepository()->find($accountId);
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {
Log::debug(sprintf('User submitted an ID (#%d), which is a "%s", so this is not a valid source.', $accountId, $search->accountType->type));
Log::debug(sprintf('Firefly III accepts ID #%d as valid account data.', $accountId));
@@ -153,7 +153,7 @@ trait DepositValidation
// if user submits an IBAN:
if (null !== $accountIban) {
$search = $this->accountRepository->findByIbanNull($accountIban, $validTypes);
$search = $this->getRepository()->findByIbanNull($accountIban, $validTypes);
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {
Log::debug(sprintf('User submitted IBAN ("%s"), which is a "%s", so this is not a valid source.', $accountIban, $search->accountType->type));
$result = false;
@@ -167,7 +167,7 @@ trait DepositValidation
// if user submits a number:
if (null !== $accountNumber && '' !== $accountNumber) {
$search = $this->accountRepository->findByAccountNumber($accountNumber, $validTypes);
$search = $this->getRepository()->findByAccountNumber($accountNumber, $validTypes);
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {
Log::debug(
sprintf('User submitted number ("%s"), which is a "%s", so this is not a valid source.', $accountNumber, $search->accountType->type)

View File

@@ -115,7 +115,7 @@ trait OBValidation
// return false.
if (null !== $accountId && null === $accountName) {
Log::debug('Source ID is not null, but name is null.');
$search = $this->accountRepository->find($accountId);
$search = $this->getRepository()->find($accountId);
// the source resulted in an account, but it's not of a valid type.
if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) {

View File

@@ -108,7 +108,7 @@ trait WithdrawalValidation
// if there's an ID it must be of the "validTypes".
if (null !== $accountId && 0 !== $accountId) {
$found = $this->accountRepository->find($accountId);
$found = $this->getRepository()->find($accountId);
if (null !== $found) {
$type = $found->accountType->type;
if (in_array($type, $validTypes, true)) {

View File

@@ -26,9 +26,10 @@ namespace FireflyIII\Validation;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\UserGroups\Account\AccountRepositoryInterface as UserGroupAccountRepositoryInterface;
use FireflyIII\User;
use FireflyIII\Validation\Account\AccountValidatorProperties;
use FireflyIII\Validation\Account\DepositValidation;
use FireflyIII\Validation\Account\LiabilityValidation;
use FireflyIII\Validation\Account\OBValidation;
@@ -42,7 +43,6 @@ use Illuminate\Support\Facades\Log;
*/
class AccountValidator
{
use AccountValidatorProperties;
use WithdrawalValidation;
use DepositValidation;
use TransferValidation;
@@ -50,28 +50,32 @@ class AccountValidator
use OBValidation;
use LiabilityValidation;
public bool $createMode;
public string $destError;
public ?Account $destination;
public ?Account $source;
public string $sourceError;
private AccountRepositoryInterface $accountRepository;
private array $combinations;
private string $transactionType;
private User $user;
public bool $createMode;
public string $destError;
public ?Account $destination;
public ?Account $source;
public string $sourceError;
private AccountRepositoryInterface $accountRepository;
private array $combinations;
private string $transactionType;
private bool $useUserGroupRepository = false;
private User $user;
private UserGroup $userGroup;
private UserGroupAccountRepositoryInterface $userGroupAccountRepository;
/**
* AccountValidator constructor.
*/
public function __construct()
{
$this->createMode = false;
$this->destError = 'No error yet.';
$this->sourceError = 'No error yet.';
$this->combinations = config('firefly.source_dests');
$this->source = null;
$this->destination = null;
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->createMode = false;
$this->destError = 'No error yet.';
$this->sourceError = 'No error yet.';
$this->combinations = config('firefly.source_dests');
$this->source = null;
$this->destination = null;
$this->accountRepository = app(AccountRepositoryInterface::class);
$this->userGroupAccountRepository = app(UserGroupAccountRepositoryInterface::class);
}
/**
@@ -126,6 +130,19 @@ class AccountValidator
{
$this->user = $user;
$this->accountRepository->setUser($user);
$this->useUserGroupRepository = false;
}
/**
* @param UserGroup $userGroup
*
* @return void
*/
public function setUserGroup(UserGroup $userGroup): void
{
$this->userGroup = $userGroup;
$this->userGroupAccountRepository->setUserGroup($userGroup);
$this->useUserGroupRepository = true;
}
/**
@@ -265,7 +282,7 @@ class AccountValidator
// find by ID
if (null !== $accountId && $accountId > 0) {
$first = $this->accountRepository->find($accountId);
$first = $this->getRepository()->find($accountId);
$accountType = null === $first ? 'invalid' : $first->accountType->type;
$check = in_array($accountType, $validTypes, true);
$check = $inverse ? !$check : $check; // reverse the validation check if necessary.
@@ -277,7 +294,7 @@ class AccountValidator
// find by iban
if (null !== $accountIban && '' !== (string)$accountIban) {
$first = $this->accountRepository->findByIbanNull($accountIban, $validTypes);
$first = $this->getRepository()->findByIbanNull($accountIban, $validTypes);
$accountType = null === $first ? 'invalid' : $first->accountType->type;
$check = in_array($accountType, $validTypes, true);
$check = $inverse ? !$check : $check; // reverse the validation check if necessary.
@@ -289,7 +306,7 @@ class AccountValidator
// find by number
if (null !== $accountNumber && '' !== (string)$accountNumber) {
$first = $this->accountRepository->findByAccountNumber($accountNumber, $validTypes);
$first = $this->getRepository()->findByAccountNumber($accountNumber, $validTypes);
$accountType = null === $first ? 'invalid' : $first->accountType->type;
$check = in_array($accountType, $validTypes, true);
$check = $inverse ? !$check : $check; // reverse the validation check if necessary.
@@ -301,7 +318,7 @@ class AccountValidator
// find by name:
if ('' !== (string)$accountName) {
$first = $this->accountRepository->findByName($accountName, $validTypes);
$first = $this->getRepository()->findByName($accountName, $validTypes);
if (null !== $first) {
app('log')->debug(sprintf('Name: Found %s account #%d ("%s", IBAN "%s")', $first->accountType->type, $first->id, $first->name, $first->iban ?? 'no iban'));
return $first;
@@ -311,4 +328,16 @@ class AccountValidator
return null;
}
/**
* @return AccountRepositoryInterface|UserGroupAccountRepositoryInterface
*/
private function getRepository(): AccountRepositoryInterface | UserGroupAccountRepositoryInterface
{
if ($this->useUserGroupRepository) {
return $this->userGroupAccountRepository;
}
return $this->accountRepository;
}
}

View File

@@ -27,7 +27,6 @@ namespace FireflyIII\Validation\Administration;
use FireflyIII\Enums\UserRoleEnum;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\UserRole;
use FireflyIII\Repositories\User\UserRepositoryInterface;
use FireflyIII\User;
use Illuminate\Auth\AuthenticationException;

View File

@@ -47,7 +47,6 @@ use PragmaRX\Google2FA\Exceptions\IncompatibleWithGoogleAuthenticatorException;
use PragmaRX\Google2FA\Exceptions\InvalidCharactersException;
use PragmaRX\Google2FA\Exceptions\SecretKeyTooShortException;
use ValueError;
use function is_string;
/**

View File

@@ -30,7 +30,9 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Models\UserGroup;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\Validator;
@@ -42,17 +44,20 @@ trait TransactionValidation
/**
* Validates the given account information. Switches on given transaction type.
*
* @param Validator $validator
* Inclusion of user and/or group is optional.
*
* @param Validator $validator
* @param User|null $user
* @param UserGroup|null $userGroup
*/
public function validateAccountInformation(Validator $validator): void
public function validateAccountInformation(Validator $validator, User $user = null, UserGroup $userGroup = null): void
{
if ($validator->errors()->count() > 0) {
return;
}
Log::debug('Now in validateAccountInformation (TransactionValidation) ()');
$transactions = $this->getTransactionsArray($validator);
$data = $validator->getData();
$transactions = $this->getTransactionsArray($validator);
$data = $validator->getData();
$transactionType = $data['type'] ?? 'invalid';
Log::debug(sprintf('Going to loop %d transaction(s)', count($transactions)));
@@ -61,6 +66,8 @@ trait TransactionValidation
* @var array $transaction
*/
foreach ($transactions as $index => $transaction) {
$transaction['user'] = $user;
$transaction['user_group'] = $userGroup;
if (!is_int($index)) {
continue;
}
@@ -107,6 +114,13 @@ trait TransactionValidation
/** @var AccountValidator $accountValidator */
$accountValidator = app(AccountValidator::class);
if (array_key_exists('user', $transaction) && null !== $transaction['user']) {
$accountValidator->setUser($transaction['user']);
}
if (array_key_exists('user_group', $transaction) && null !== $transaction['user_group']) {
$accountValidator->setUserGroup($transaction['user_group']);
}
$transactionType = $transaction['type'] ?? $transactionType;
$accountValidator->setTransactionType($transactionType);
@@ -204,7 +218,8 @@ trait TransactionValidation
array $transaction,
string $transactionType,
int $index
): void {
): void
{
Log::debug('Now in sanityCheckForeignCurrency()');
if (0 !== $validator->errors()->count()) {
Log::debug('Already have errors, return');

View File

@@ -50,6 +50,7 @@ return [
'invalid_transaction_type' => 'Invalid transaction type.',
'invalid_selection' => 'Your selection is invalid.',
'belongs_user' => 'This value is invalid for this field.',
'belongs_user_or_user_group' => 'This value is invalid for this field.',
'at_least_one_transaction' => 'Need at least one transaction.',
'recurring_transaction_id' => 'Need at least one transaction.',
'need_id_to_match' => 'You need to submit this entry with an ID for the API to be able to match it.',