diff --git a/app/Api/V1/Controllers/User/PreferencesController.php b/app/Api/V1/Controllers/User/PreferencesController.php index d62152d5f9..72be0bee41 100644 --- a/app/Api/V1/Controllers/User/PreferencesController.php +++ b/app/Api/V1/Controllers/User/PreferencesController.php @@ -86,7 +86,7 @@ class PreferencesController extends Controller $manager = $this->getManager(); if ('currencyPreference' === $preference->name) { - throw new FireflyException('Please use api/v1/currencies/default instead.'); + throw new FireflyException('Please use api/v1/currencies/native instead.'); } /** @var PreferenceTransformer $transformer */ @@ -161,7 +161,7 @@ class PreferencesController extends Controller public function update(PreferenceUpdateRequest $request, Preference $preference): JsonResponse { if ('currencyPreference' === $preference->name) { - throw new FireflyException('Please use api/v1/currencies/default instead.'); + throw new FireflyException('Please use api/v1/currencies/native instead.'); } $manager = $this->getManager(); diff --git a/app/Api/V1/Requests/Models/Account/UpdateRequest.php b/app/Api/V1/Requests/Models/Account/UpdateRequest.php index ec219e3938..476386717c 100644 --- a/app/Api/V1/Requests/Models/Account/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Account/UpdateRequest.php @@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests\Models\Account; use FireflyIII\Models\Account; use FireflyIII\Models\Location; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Rules\IsBoolean; use FireflyIII\Rules\UniqueAccountNumber; use FireflyIII\Rules\UniqueIban; @@ -33,6 +34,8 @@ use FireflyIII\Support\Request\AppendsLocationData; use FireflyIII\Support\Request\ChecksLogin; use FireflyIII\Support\Request\ConvertsDataTypes; use Illuminate\Foundation\Http\FormRequest; +use Illuminate\Support\Facades\Log; +use Illuminate\Validation\Validator; /** * Class UpdateRequest @@ -86,7 +89,7 @@ class UpdateRequest extends FormRequest $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); - $rules = [ + $rules = [ 'name' => sprintf('min:1|max:1024|uniqueAccountForUser:%d', $account->id), 'type' => sprintf('in:%s', $types), 'iban' => ['iban', 'nullable', new UniqueIban($account, $this->convertString('type'))], @@ -112,4 +115,34 @@ class UpdateRequest extends FormRequest return Location::requestRules($rules); } + + /** + * Configure the validator instance with special rules for after the basic validation rules. + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator): void { + // validate start before end only if both are there. + $data = $validator->getData(); + /** @var Account $account */ + $account = $this->route()->parameter('account'); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $currency = $repository->getAccountCurrency($account); + + // how many piggies are attached? + $piggyBanks = $account->piggyBanks()->count(); + if($piggyBanks > 0 && array_key_exists('currency_code', $data) && $data['currency_code'] !== $currency->code) { + $validator->errors()->add('currency_code', (string) trans('validation.piggy_no_change_currency')); + } + if($piggyBanks > 0 && array_key_exists('currency_id', $data) && (int) $data['currency_id'] !== $currency->id) { + $validator->errors()->add('currency_id', (string) trans('validation.piggy_no_change_currency')); + } + } + ); + if ($validator->fails()) { + Log::channel('audit')->error(sprintf('Validation errors in %s', __CLASS__), $validator->errors()->toArray()); + } + } } diff --git a/app/Api/V1/Requests/Models/Tag/UpdateRequest.php b/app/Api/V1/Requests/Models/Tag/UpdateRequest.php index be069c289e..9355edad15 100644 --- a/app/Api/V1/Requests/Models/Tag/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Tag/UpdateRequest.php @@ -63,7 +63,6 @@ class UpdateRequest extends FormRequest { /** @var Tag $tag */ $tag = $this->route()->parameter('tagOrId'); - // TODO check if uniqueObjectForUser is obsolete $rules = [ 'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id, 'description' => 'min:1|nullable|max:32768', diff --git a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php index 9e4f63a05b..020254ad7a 100644 --- a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Transaction; +use FireflyIII\Models\Location; use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; use FireflyIII\Rules\IsDateOrTime; @@ -89,6 +90,11 @@ class StoreRequest extends FormRequest 'currency_id' => $this->integerFromValue((string) $object['currency_id']), 'currency_code' => $this->clearString((string) $object['currency_code']), + // location + 'latitude' => $this->floatFromValue((string) $object['latitude']), + 'longitude' => $this->floatFromValue((string) $object['longitude']), + 'zoom_level' => $this->integerFromValue((string) $object['zoom_level']), + // foreign currency info: 'foreign_currency_id' => $this->integerFromValue((string) $object['foreign_currency_id']), 'foreign_currency_code' => $this->clearString((string) $object['foreign_currency_code']), @@ -171,13 +177,18 @@ class StoreRequest extends FormRequest { app('log')->debug('Collect rules of TransactionStoreRequest'); $validProtocols = config('firefly.valid_url_protocols'); - + $locationRules = Location::requestRules([]); return [ // basic fields for group: 'group_title' => 'min:1|max:1000|nullable', 'error_if_duplicate_hash' => [new IsBoolean()], 'apply_rules' => [new IsBoolean()], + // location rules + 'transactions.*.latitude' => $locationRules['latitude'], + 'transactions.*.longitude' => $locationRules['longitude'], + 'transactions.*.zoom_level' => $locationRules['zoom_level'], + // transaction rules (in array for splits): 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', 'transactions.*.date' => ['required', new IsDateOrTime()], diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 2902531db1..86a56765a3 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -576,7 +576,7 @@ class TransactionJournalFactory private function storeLocation(TransactionJournal $journal, NullArrayObject $data): void { - if (true === $data['store_location']) { + if(null !== $data['longitude'] && null !== $data['latitude'] && null !== $data['zoom_level']) { $location = new Location(); $location->longitude = $data['longitude']; $location->latitude = $data['latitude']; diff --git a/app/Support/Request/ConvertsDataTypes.php b/app/Support/Request/ConvertsDataTypes.php index 62bd8beef1..8f4c979904 100644 --- a/app/Support/Request/ConvertsDataTypes.php +++ b/app/Support/Request/ConvertsDataTypes.php @@ -386,6 +386,18 @@ trait ConvertsDataTypes return (int) $string; } + protected function floatFromValue(?string $string): ?float + { + if (null === $string) { + return null; + } + if ('' === $string) { + return null; + } + + return (float) $string; + } + /** * Return integer value, or NULL when it's not set. */ diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 0ef3c88d1a..44640c380c 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -177,6 +177,7 @@ return [ 'unique_existing_webhook' => 'You already have another webhook with this combination of URL, trigger, response and delivery.', 'same_account_type' => 'Both accounts must be of the same account type', 'same_account_currency' => 'Both accounts must have the same currency setting', + 'piggy_no_change_currency' => 'Because there are piggy banks linked to this account, you cannot change the currency of the account.',