diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index 69dea0db2e..18e8462f02 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -85,12 +85,11 @@ abstract class Controller extends BaseController { $bag = new ParameterBag(); $page = (int)request()->get('page'); - if ($page < 1) { $page = 1; } - if ($page > (2 ^ 16)) { - $page = (2 ^ 16); + if ($page > pow(2, 16)) { + $page = pow(2, 16); } $bag->set('page', $page); diff --git a/app/Api/V1/Requests/Data/DestroyRequest.php b/app/Api/V1/Requests/Data/DestroyRequest.php index 299ecf138b..0f186ffd4e 100644 --- a/app/Api/V1/Requests/Data/DestroyRequest.php +++ b/app/Api/V1/Requests/Data/DestroyRequest.php @@ -57,7 +57,7 @@ class DestroyRequest extends FormRequest ',not_assets_liabilities'; return [ - 'objects' => sprintf('required|min:1|string|in:%s', $valid), + 'objects' => sprintf('required|max:255|min:1|string|in:%s', $valid), 'unused' => 'in:true,false', ]; } diff --git a/app/Api/V1/Requests/Data/Export/ExportRequest.php b/app/Api/V1/Requests/Data/Export/ExportRequest.php index fc264e4f8d..6c6f80d3ba 100644 --- a/app/Api/V1/Requests/Data/Export/ExportRequest.php +++ b/app/Api/V1/Requests/Data/Export/ExportRequest.php @@ -73,7 +73,7 @@ class ExportRequest extends FormRequest { return [ 'type' => 'in:csv', - 'accounts' => 'min:1', + 'accounts' => 'min:1|max:65536', 'start' => 'date|before:end', 'end' => 'date|after:start', ]; diff --git a/app/Api/V1/Requests/Models/Account/StoreRequest.php b/app/Api/V1/Requests/Models/Account/StoreRequest.php index c798731236..7be849258f 100644 --- a/app/Api/V1/Requests/Models/Account/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Account/StoreRequest.php @@ -103,8 +103,8 @@ class StoreRequest extends FormRequest $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $type = $this->convertString('type'); $rules = [ - 'name' => 'required|min:1|uniqueAccountForUser', - 'type' => 'required|min:1|'.sprintf('in:%s', $types), + 'name' => 'required|max:1024|min:1|uniqueAccountForUser', + 'type' => 'required|max:1024|min:1|'.sprintf('in:%s', $types), 'iban' => ['iban', 'nullable', new UniqueIban(null, $type)], 'bic' => 'bic|nullable', 'account_number' => ['between:1,255', 'nullable', new UniqueAccountNumber(null, $type)], @@ -120,7 +120,7 @@ class StoreRequest extends FormRequest 'credit_card_type' => sprintf('nullable|in:%s|required_if:account_role,ccAsset', $ccPaymentTypes), 'monthly_payment_date' => 'nullable|date|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', 'liability_type' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:loan,debt,mortgage', - 'liability_amount' => 'required_with:liability_start_date|min:0|numeric', + 'liability_amount' => 'required_with:liability_start_date|min:0|numeric|max:1000000000', 'liability_start_date' => 'required_with:liability_amount|date', 'liability_direction' => 'nullable|required_if:type,liability|required_if:type,liabilities|in:credit,debit', 'interest' => 'between:0,100|numeric', diff --git a/app/Api/V1/Requests/Models/Account/UpdateRequest.php b/app/Api/V1/Requests/Models/Account/UpdateRequest.php index 0341e52346..a9c27e6485 100644 --- a/app/Api/V1/Requests/Models/Account/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Account/UpdateRequest.php @@ -94,7 +94,7 @@ class UpdateRequest extends FormRequest $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $rules = [ - 'name' => sprintf('min:1|uniqueAccountForUser:%d', $account->id), + 'name' => sprintf('min:1|max:1024|uniqueAccountForUser:%d', $account->id), 'type' => sprintf('in:%s', $types), 'iban' => ['iban', 'nullable', new UniqueIban($account, $this->convertString('type'))], 'bic' => 'bic|nullable', @@ -104,7 +104,7 @@ class UpdateRequest extends FormRequest 'virtual_balance' => 'numeric|nullable', 'order' => 'numeric|nullable', 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'active' => [new IsBoolean()], 'include_net_worth' => [new IsBoolean()], 'account_role' => sprintf('in:%s|nullable|required_if:type,asset', $accountRoles), diff --git a/app/Api/V1/Requests/Models/AvailableBudget/Request.php b/app/Api/V1/Requests/Models/AvailableBudget/Request.php index b839f0b26c..454ab77167 100644 --- a/app/Api/V1/Requests/Models/AvailableBudget/Request.php +++ b/app/Api/V1/Requests/Models/AvailableBudget/Request.php @@ -67,7 +67,7 @@ class Request extends FormRequest { return [ 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'amount' => 'numeric|gt:0', 'start' => 'date', 'end' => 'date', diff --git a/app/Api/V1/Requests/Models/Bill/StoreRequest.php b/app/Api/V1/Requests/Models/Bill/StoreRequest.php index 61ce2be480..d6f7aa5652 100644 --- a/app/Api/V1/Requests/Models/Bill/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Bill/StoreRequest.php @@ -82,7 +82,7 @@ class StoreRequest extends FormRequest 'amount_min' => 'numeric|gt:0|required', 'amount_max' => 'numeric|gt:0|required', 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'date' => 'date|required', 'end_date' => 'date|after:date', 'extension_date' => 'date|after:date', diff --git a/app/Api/V1/Requests/Models/Bill/UpdateRequest.php b/app/Api/V1/Requests/Models/Bill/UpdateRequest.php index 482fe68a00..ded92b920c 100644 --- a/app/Api/V1/Requests/Models/Bill/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Bill/UpdateRequest.php @@ -84,7 +84,7 @@ class UpdateRequest extends FormRequest 'amount_min' => 'numeric|gt:0', 'amount_max' => 'numeric|gt:0', 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'date' => 'date', 'end_date' => 'date|after:date', 'extension_date' => 'date|after:date', diff --git a/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php b/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php index b3a1c30ae4..1c003221ef 100644 --- a/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php +++ b/app/Api/V1/Requests/Models/BudgetLimit/StoreRequest.php @@ -65,7 +65,7 @@ class StoreRequest extends FormRequest 'end' => 'required|after:start|date', 'amount' => 'required|gt:0', 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', ]; } } diff --git a/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php b/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php index 552ecb8201..d863916ae5 100644 --- a/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/BudgetLimit/UpdateRequest.php @@ -69,7 +69,7 @@ class UpdateRequest extends FormRequest 'end' => 'date', 'amount' => 'gt:0', 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'currency_code' => 'min:3|max:51|exists:transaction_currencies,code', ]; } diff --git a/app/Api/V1/Requests/Models/ObjectGroup/UpdateRequest.php b/app/Api/V1/Requests/Models/ObjectGroup/UpdateRequest.php index 9ea7f8dbf0..9a9f96d8da 100644 --- a/app/Api/V1/Requests/Models/ObjectGroup/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/ObjectGroup/UpdateRequest.php @@ -63,7 +63,7 @@ class UpdateRequest extends FormRequest $objectGroup = $this->route()->parameter('objectGroup'); return [ - 'title' => sprintf('min:1|uniqueObjectGroup:%d', $objectGroup->id), + 'title' => sprintf('max:1024|min:1|uniqueObjectGroup:%d', $objectGroup->id), 'order' => 'numeric', ]; } diff --git a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php index 6892791352..9b34a07ad9 100644 --- a/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/StoreRequest.php @@ -158,9 +158,9 @@ class StoreRequest extends FormRequest 'transactions.*.amount' => 'required|numeric|gt:0', 'transactions.*.foreign_amount' => 'nullable|numeric|gt:0', 'transactions.*.currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], 'transactions.*.source_name' => 'between:1,255|nullable', 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], diff --git a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php index 2677ab6157..0cd1464794 100644 --- a/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Recurrence/UpdateRequest.php @@ -172,9 +172,9 @@ class UpdateRequest extends FormRequest 'transactions.*.amount' => 'numeric|gt:0', 'transactions.*.foreign_amount' => 'nullable|numeric|gt:0', 'transactions.*.currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser()], 'transactions.*.source_name' => 'between:1,255|nullable', 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser()], diff --git a/app/Api/V1/Requests/Models/Rule/StoreRequest.php b/app/Api/V1/Requests/Models/Rule/StoreRequest.php index f74df055c3..a6087c9a17 100644 --- a/app/Api/V1/Requests/Models/Rule/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Rule/StoreRequest.php @@ -130,7 +130,7 @@ class StoreRequest extends FormRequest 'rule_group_title' => 'nullable|between:1,255|required_without:rule_group_id|belongsToUser:rule_groups,title', 'trigger' => 'required|in:store-journal,update-journal', 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers), - 'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue', + 'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024', 'triggers.*.stop_processing' => [new IsBoolean()], 'triggers.*.active' => [new IsBoolean()], 'actions.*.type' => 'required|in:'.implode(',', $validActions), diff --git a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php index ab51cdd56c..29449397b0 100644 --- a/app/Api/V1/Requests/Models/Rule/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Rule/UpdateRequest.php @@ -147,7 +147,7 @@ class UpdateRequest extends FormRequest 'rule_group_title' => 'nullable|between:1,255|belongsToUser:rule_groups,title', 'trigger' => 'in:store-journal,update-journal', 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers), - 'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue', + 'triggers.*.value' => 'required_if:actions.*.type,'.$contextTriggers.'|min:1|ruleTriggerValue|max:1024', 'triggers.*.stop_processing' => [new IsBoolean()], 'triggers.*.active' => [new IsBoolean()], 'actions.*.type' => 'required|in:'.implode(',', $validActions), diff --git a/app/Api/V1/Requests/Models/Tag/StoreRequest.php b/app/Api/V1/Requests/Models/Tag/StoreRequest.php index 2717114b0b..5f0ce7737b 100644 --- a/app/Api/V1/Requests/Models/Tag/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Tag/StoreRequest.php @@ -65,8 +65,8 @@ class StoreRequest extends FormRequest public function rules(): array { $rules = [ - 'tag' => 'required|min:1|uniqueObjectForUser:tags,tag', - 'description' => 'min:1|nullable', + 'tag' => 'required|min:1|uniqueObjectForUser:tags,tag|max:1024', + 'description' => 'min:1|nullable|max:65536', 'date' => 'date|nullable', ]; diff --git a/app/Api/V1/Requests/Models/Tag/UpdateRequest.php b/app/Api/V1/Requests/Models/Tag/UpdateRequest.php index c63bebdc87..03eb2d2573 100644 --- a/app/Api/V1/Requests/Models/Tag/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Tag/UpdateRequest.php @@ -71,8 +71,8 @@ class UpdateRequest extends FormRequest $tag = $this->route()->parameter('tagOrId'); // TODO check if uniqueObjectForUser is obsolete $rules = [ - 'tag' => 'min:1|uniqueObjectForUser:tags,tag,'.$tag->id, - 'description' => 'min:1|nullable', + 'tag' => 'min:1|max:1024|uniqueObjectForUser:tags,tag,'.$tag->id, + 'description' => 'min:1|nullable|max:65536', 'date' => 'date|nullable', ]; diff --git a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php index f6c1d2803c..d63cffcfcb 100644 --- a/app/Api/V1/Requests/Models/Transaction/StoreRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/StoreRequest.php @@ -188,9 +188,9 @@ class StoreRequest extends FormRequest // currency info 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id|nullable', - 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code|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:3|exists:transaction_currencies,code|nullable', + 'transactions.*.foreign_currency_code' => 'min:3|max:51|exists:transaction_currencies,code|nullable', // amount 'transactions.*.amount' => 'required|numeric|gt:0', @@ -225,25 +225,25 @@ class StoreRequest extends FormRequest // other interesting fields 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1,max:50000|nullable', + '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', + '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', + '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', diff --git a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php index 2fc4dfdf18..801a8f03af 100644 --- a/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/Transaction/UpdateRequest.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests\Models\Transaction; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; use FireflyIII\Rules\BelongsUser; use FireflyIII\Rules\IsBoolean; @@ -61,6 +62,7 @@ class UpdateRequest extends FormRequest */ public function getAll(): array { + Log::debug(sprintf('Now in %s', __METHOD__)); $this->integerFields = [ 'order', @@ -163,6 +165,9 @@ class UpdateRequest extends FormRequest /** @var array $transaction */ foreach ($this->get('transactions') as $transaction) { + if(!is_array($transaction)) { + throw new FireflyException('Invalid data submitted: transaction is not array.'); + } // default response is to update nothing in the transaction: $current = []; $current = $this->getIntegerData($current, $transaction); @@ -330,9 +335,9 @@ class UpdateRequest extends FormRequest // currency info 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.currency_code' => 'min:3|max:51|exists:transaction_currencies,code', 'transactions.*.foreign_currency_id' => 'nullable|numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_code' => 'nullable|min:3|max:51|exists:transaction_currencies,code', // amount 'transactions.*.amount' => 'numeric|gt:0|max:100000000000', @@ -359,25 +364,25 @@ class UpdateRequest extends FormRequest // other interesting fields 'transactions.*.reconciled' => [new IsBoolean()], - 'transactions.*.notes' => 'min:1,max:50000|nullable', + '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', + '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', + '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', @@ -398,6 +403,7 @@ class UpdateRequest extends FormRequest */ public function withValidator(Validator $validator): void { + Log::debug('Now in withValidator'); /** @var TransactionGroup $transactionGroup */ $transactionGroup = $this->route()->parameter('transactionGroup'); $validator->after( diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/StoreRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/StoreRequest.php index 7314acbf27..479348c85b 100644 --- a/app/Api/V1/Requests/Models/TransactionCurrency/StoreRequest.php +++ b/app/Api/V1/Requests/Models/TransactionCurrency/StoreRequest.php @@ -75,7 +75,7 @@ class StoreRequest extends FormRequest 'name' => 'required|between:1,255|unique:transaction_currencies,name', 'code' => 'required|between:3,51|unique:transaction_currencies,code', 'symbol' => 'required|between:1,51|unique:transaction_currencies,symbol', - 'decimal_places' => 'between:0,20|numeric|min:0|max:20', + 'decimal_places' => 'between:0,20|numeric|min:0|max:12', 'enabled' => [new IsBoolean()], 'default' => [new IsBoolean()], diff --git a/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php b/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php index f9086b99ca..5e673d42b4 100644 --- a/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/TransactionCurrency/UpdateRequest.php @@ -74,7 +74,7 @@ class UpdateRequest extends FormRequest 'name' => sprintf('between:1,255|unique:transaction_currencies,name,%d', $currency->id), 'code' => sprintf('between:3,51|unique:transaction_currencies,code,%d', $currency->id), 'symbol' => sprintf('between:1,51|unique:transaction_currencies,symbol,%d', $currency->id), - 'decimal_places' => 'between:0,20|numeric|min:0|max:20', + 'decimal_places' => 'between:0,20|numeric|min:0|max:12', 'enabled' => [new IsBoolean()], 'default' => [new IsBoolean()], ]; diff --git a/app/Api/V1/Requests/Models/TransactionLinkType/StoreRequest.php b/app/Api/V1/Requests/Models/TransactionLinkType/StoreRequest.php index 4d819b3740..cc9a4a0dcf 100644 --- a/app/Api/V1/Requests/Models/TransactionLinkType/StoreRequest.php +++ b/app/Api/V1/Requests/Models/TransactionLinkType/StoreRequest.php @@ -59,9 +59,9 @@ class StoreRequest extends FormRequest public function rules(): array { return [ - 'name' => 'required|unique:link_types,name|min:1', - 'outward' => 'required|unique:link_types,outward|min:1|different:inward', - 'inward' => 'required|unique:link_types,inward|min:1|different:outward', + 'name' => 'required|unique:link_types,name|min:1|max:1024', + 'outward' => 'required|unique:link_types,outward|min:1|different:inward|max:1024', + 'inward' => 'required|unique:link_types,inward|min:1|different:outward|max:1024', ]; } } diff --git a/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php b/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php index d2ca921c43..b4b3866747 100644 --- a/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php +++ b/app/Api/V1/Requests/Models/TransactionLinkType/UpdateRequest.php @@ -64,9 +64,9 @@ class UpdateRequest extends FormRequest $linkType = $this->route()->parameter('linkType'); return [ - 'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1'], - 'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1'], - 'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1'], + 'name' => [Rule::unique('link_types', 'name')->ignore($linkType->id), 'min:1','max:1024'], + 'outward' => ['different:inward', Rule::unique('link_types', 'outward')->ignore($linkType->id), 'min:1','max:1024'], + 'inward' => ['different:outward', Rule::unique('link_types', 'inward')->ignore($linkType->id), 'min:1','max:1024'], ]; } } diff --git a/app/Console/Commands/Correction/CorrectAmounts.php b/app/Console/Commands/Correction/CorrectAmounts.php new file mode 100644 index 0000000000..56692c0821 --- /dev/null +++ b/app/Console/Commands/Correction/CorrectAmounts.php @@ -0,0 +1,263 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\AutoBudget; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\Bill; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\CurrencyExchangeRate; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\PiggyBankRepetition; +use FireflyIII\Models\RecurrenceTransaction; +use FireflyIII\Models\RuleTrigger; +use Illuminate\Console\Command; + +/** + * Class ReportSkeleton + */ +class CorrectAmounts extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'This command makes sure positive and negative amounts are recorded correctly.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-amount-pos-neg'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + // auto budgets must be positive + $this->fixAutoBudgets(); + // available budgets must be positive + $this->fixAvailableBudgets(); + // bills must be positive (both amounts) + $this->fixBills(); + // budget limits must be positive + $this->fixBudgetLimits(); + // currency_exchange_rates must be positive + $this->fixExchangeRates(); + // piggy_bank_repetitions must be positive + $this->fixRepetitions(); + // piggy_banks must be positive + $this->fixPiggyBanks(); + // recurrences_transactions amount must be positive + $this->fixRecurrences(); + // rule_triggers must be positive or zero (amount_less, amount_more, amount_is) + $this->fixRuleTriggers(); + + + return 0; + } + + /** + * @return void + */ + private function fixAutoBudgets(): void + { + $set = AutoBudget::where('amount', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All auto budget amounts are positive.'); + return; + } + /** @var AutoBudget $item */ + foreach ($set as $item) { + $item->amount = app('steam')->positive((string)$item->amount); + $item->save(); + } + $this->line(sprintf('Corrected %d auto budget amount(s).', $count)); + } + + /** + * @return void + */ + private function fixAvailableBudgets(): void + { + $set = AvailableBudget::where('amount', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All available budget amounts are positive.'); + return; + } + /** @var AvailableBudget $item */ + foreach ($set as $item) { + $item->amount = app('steam')->positive((string)$item->amount); + $item->save(); + } + $this->line(sprintf('Corrected %d available budget amount(s).', $count)); + } + + /** + * @return void + */ + private function fixBills(): void + { + $set = Bill::where('amount_min', '<', 0)->orWhere('amount_max', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All bill amounts are positive.'); + return; + } + /** @var Bill $item */ + foreach ($set as $item) { + $item->amount_min = app('steam')->positive((string)$item->amount_min); + $item->amount_max = app('steam')->positive((string)$item->amount_max); + $item->save(); + } + } + + /** + * @return void + */ + private function fixBudgetLimits(): void + { + $set = BudgetLimit::where('amount', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All budget limit amounts are positive.'); + return; + } + /** @var BudgetLimit $item */ + foreach ($set as $item) { + $item->amount = app('steam')->positive((string)$item->amount); + $item->save(); + } + $this->line(sprintf('Corrected %d budget limit amount(s).', $count)); + } + + /** + * @return void + */ + private function fixExchangeRates(): void + { + $set = CurrencyExchangeRate::where('rate', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All currency exchange rates are positive.'); + return; + } + /** @var BudgetLimit $item */ + foreach ($set as $item) { + $item->rate = app('steam')->positive((string)$item->rate); + $item->save(); + } + $this->line(sprintf('Corrected %d currency exchange rate(s).', $count)); + } + + /** + * @return void + */ + private function fixRepetitions(): void + { + $set = PiggyBankRepetition::where('currentamount', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All piggy bank repetition amounts are positive.'); + return; + } + /** @var PiggyBankRepetition $item */ + foreach ($set as $item) { + $item->currentamount = app('steam')->positive((string)$item->currentamount); + $item->save(); + } + $this->line(sprintf('Corrected %d piggy bank repetition amount(s).', $count)); + } + + /** + * @return void + */ + private function fixPiggyBanks(): void + { + $set = PiggyBank::where('targetamount', '<', 0)->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All piggy bank amounts are positive.'); + return; + } + /** @var PiggyBankRepetition $item */ + foreach ($set as $item) { + $item->targetamount = app('steam')->positive((string)$item->targetamount); + $item->save(); + } + $this->line(sprintf('Corrected %d piggy bank amount(s).', $count)); + } + + /** + * @return void + */ + private function fixRecurrences(): void + { + $set = RecurrenceTransaction::where('amount', '<', 0) + ->orWhere('foreign_amount', '<', 0) + ->get(); + $count = $set->count(); + if (0 === $count) { + $this->info('Correct: All recurring transaction amounts are positive.'); + return; + } + /** @var PiggyBankRepetition $item */ + foreach ($set as $item) { + $item->amount = app('steam')->positive((string)$item->amount); + $item->foreign_amount = app('steam')->positive((string)$item->foreign_amount); + $item->save(); + } + $this->line(sprintf('Corrected %d recurring transaction amount(s).', $count)); + } + + /** + * @return void + */ + private function fixRuleTriggers(): void + { + $set = RuleTrigger::whereIn('trigger_type', ['amount_less', 'amount_more', 'amount_is'])->get(); + $fixed = 0; + /** @var RuleTrigger $item */ + foreach ($set as $item) { + // basic check: + if (-1 === bccomp((string)$item->trigger_value, '0')) { + $fixed++; + $item->trigger_value = app('steam')->positive((string)$item->trigger_value); + $item->save(); + } + } + if (0 === $fixed) { + $this->info('Correct: All rule trigger amounts are positive.'); + return; + } + $this->line(sprintf('Corrected %d rule trigger amount(s).', $fixed)); + } + +} diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php index 1d34acd1be..6c8c9bf1d8 100644 --- a/app/Console/Commands/Correction/CorrectDatabase.php +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -52,8 +52,10 @@ class CorrectDatabase extends Command */ public function handle(): int { + $this->line('Handle Firefly III database correction commands.'); // if table does not exist, return false if (!Schema::hasTable('users')) { + $this->error('No "users"-table, will not continue.'); return 1; } $commands = [ @@ -61,7 +63,7 @@ class CorrectDatabase extends Command 'firefly-iii:create-link-types', 'firefly-iii:create-access-tokens', 'firefly-iii:remove-bills', - 'firefly-iii:fix-negative-limits', + 'firefly-iii:fix-amount-pos-neg', 'firefly-iii:enable-currencies', 'firefly-iii:fix-transfer-budgets', 'firefly-iii:fix-uneven-amount', @@ -76,16 +78,16 @@ class CorrectDatabase extends Command 'firefly-iii:fix-ob-currencies', 'firefly-iii:fix-long-descriptions', 'firefly-iii:fix-recurring-transactions', - 'firefly-iii:restore-oauth-keys', 'firefly-iii:upgrade-group-information', 'firefly-iii:fix-transaction-types', 'firefly-iii:fix-frontpage-accounts', + // new! + 'firefly-iii:unify-group-accounts', + 'firefly-iii:trigger-credit-recalculation' ]; foreach ($commands as $command) { - $this->line(sprintf('Now executing %s', $command)); - Artisan::call($command); - $result = Artisan::output(); - echo $result; + $this->line(sprintf('Now executing command "%s"', $command)); + $this->call($command); } return 0; diff --git a/app/Console/Commands/Correction/FixBudgetLimits.php b/app/Console/Commands/Correction/FixBudgetLimits.php deleted file mode 100644 index 39405ac70f..0000000000 --- a/app/Console/Commands/Correction/FixBudgetLimits.php +++ /dev/null @@ -1,67 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands\Correction; - -use DB; -use FireflyIII\Models\BudgetLimit; -use Illuminate\Console\Command; - -/** - * Class CorrectionSkeleton - */ -class FixBudgetLimits extends Command -{ - /** - * The console command description. - * - * @var string - */ - protected $description = 'Fixes negative budget limits'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly-iii:fix-negative-limits'; - - /** - * Execute the console command. - * - * @return int - */ - public function handle(): int - { - $set = BudgetLimit::where('amount', '<', '0')->get(); - if (0 === $set->count()) { - $this->info('All budget limits are OK.'); - return 0; - } - $count = BudgetLimit::where('amount', '<', '0')->update(['amount' => DB::raw('amount * -1')]); - - $this->info(sprintf('Fixed %d budget limit(s)', $count)); - - return 0; - } -} diff --git a/app/Console/Commands/Integrity/ReportIntegrity.php b/app/Console/Commands/Integrity/ReportIntegrity.php index 42d1459b84..f6c21234f7 100644 --- a/app/Console/Commands/Integrity/ReportIntegrity.php +++ b/app/Console/Commands/Integrity/ReportIntegrity.php @@ -57,14 +57,15 @@ class ReportIntegrity extends Command return 1; } $commands = [ + 'firefly-iii:create-group-memberships', 'firefly-iii:report-empty-objects', 'firefly-iii:report-sum', + 'firefly-iii:restore-oauth-keys', + 'firefly-iii:upgrade-group-information' ]; foreach ($commands as $command) { $this->line(sprintf('Now executing %s', $command)); - Artisan::call($command); - $result = Artisan::output(); - echo $result; + $this->call($command); } return 0; diff --git a/app/Console/Commands/CreateDatabase.php b/app/Console/Commands/System/CreateDatabase.php similarity index 97% rename from app/Console/Commands/CreateDatabase.php rename to app/Console/Commands/System/CreateDatabase.php index b96a070556..0022e02a4e 100644 --- a/app/Console/Commands/CreateDatabase.php +++ b/app/Console/Commands/System/CreateDatabase.php @@ -1,8 +1,8 @@ . + */ declare(strict_types=1); -namespace FireflyIII\Console\Commands; +namespace FireflyIII\Console\Commands\System; +use FireflyIII\Console\Commands\VerifiesAccessToken; use FireflyIII\Exceptions\FireflyException; use Illuminate\Console\Command; use Illuminate\Support\Facades\DB; diff --git a/app/Console/Commands/ForceMigration.php b/app/Console/Commands/System/ForceMigration.php similarity index 70% rename from app/Console/Commands/ForceMigration.php rename to app/Console/Commands/System/ForceMigration.php index 99dcc8cb95..43c96ebe7a 100644 --- a/app/Console/Commands/ForceMigration.php +++ b/app/Console/Commands/System/ForceMigration.php @@ -1,9 +1,29 @@ . + */ declare(strict_types=1); -namespace FireflyIII\Console\Commands; +namespace FireflyIII\Console\Commands\System; +use FireflyIII\Console\Commands\VerifiesAccessToken; use FireflyIII\Exceptions\FireflyException; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/System/ScanAttachments.php similarity index 95% rename from app/Console/Commands/ScanAttachments.php rename to app/Console/Commands/System/ScanAttachments.php index 747c25f0e3..231ee3ea9e 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/System/ScanAttachments.php @@ -1,7 +1,7 @@ showLine(); $this->boxed(''); - if (null === $text) { + if (null === $text || '' === $text) { $this->boxed(sprintf('Thank you for updating to Firefly III, v%s', $version)); $this->boxedInfo('There are no extra upgrade instructions.'); $this->boxed('Firefly III should be ready for use.'); @@ -146,13 +149,13 @@ class UpgradeFireflyInstructions extends Command $text = ''; foreach (array_keys($config) as $compare) { // if string starts with: - if (str_starts_with($version, $compare)) { + if (\str_starts_with($version, $compare)) { $text = $config[$compare]; } } $this->showLine(); $this->boxed(''); - if (null === $text) { + if (null === $text || '' === $text) { $this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version)); $this->boxedInfo('There are no extra installation instructions.'); $this->boxed('Firefly III should be ready for use.'); diff --git a/app/Console/Commands/VerifySecurityAlerts.php b/app/Console/Commands/System/VerifySecurityAlerts.php similarity index 97% rename from app/Console/Commands/VerifySecurityAlerts.php rename to app/Console/Commands/System/VerifySecurityAlerts.php index bbca6a8f54..fdc3333f2c 100644 --- a/app/Console/Commands/VerifySecurityAlerts.php +++ b/app/Console/Commands/System/VerifySecurityAlerts.php @@ -2,7 +2,7 @@ /* * VerifySecurityAlerts.php - * Copyright (c) 2021 james@firefly-iii.org + * Copyright (c) 2023 james@firefly-iii.org * * This file is part of Firefly III (https://github.com/firefly-iii). * @@ -22,12 +22,12 @@ declare(strict_types=1); -namespace FireflyIII\Console\Commands; +namespace FireflyIII\Console\Commands\System; use Illuminate\Console\Command; use Illuminate\Database\QueryException; -use League\Flysystem\FilesystemException; use Illuminate\Support\Facades\Log; +use League\Flysystem\FilesystemException; use Storage; /** diff --git a/app/Console/Commands/DecryptDatabase.php b/app/Console/Commands/Upgrade/DecryptDatabase.php similarity index 98% rename from app/Console/Commands/DecryptDatabase.php rename to app/Console/Commands/Upgrade/DecryptDatabase.php index d933dc922c..45051dc6f4 100644 --- a/app/Console/Commands/DecryptDatabase.php +++ b/app/Console/Commands/Upgrade/DecryptDatabase.php @@ -1,8 +1,8 @@ callInitialCommands(); $commands = [ - // there are 14 upgrade commands. 'firefly-iii:transaction-identifiers', 'firefly-iii:migrate-to-groups', 'firefly-iii:account-currencies', @@ -75,41 +74,7 @@ class UpgradeDatabase extends Command 'firefly-iii:migrate-recurrence-type', 'firefly-iii:upgrade-liabilities', 'firefly-iii:liabilities-600', - - // there are 16 verify commands. - 'firefly-iii:fix-piggies', - 'firefly-iii:create-link-types', - 'firefly-iii:create-access-tokens', - 'firefly-iii:remove-bills', - 'firefly-iii:fix-negative-limits', - 'firefly-iii:enable-currencies', - 'firefly-iii:fix-transfer-budgets', - 'firefly-iii:fix-uneven-amount', - 'firefly-iii:delete-zero-amount', - 'firefly-iii:delete-orphaned-transactions', - 'firefly-iii:delete-empty-journals', - 'firefly-iii:delete-empty-groups', - 'firefly-iii:fix-account-types', - 'firefly-iii:fix-account-order', - 'firefly-iii:rename-meta-fields', - 'firefly-iii:fix-ob-currencies', - 'firefly-iii:fix-long-descriptions', - 'firefly-iii:fix-recurring-transactions', - 'firefly-iii:unify-group-accounts', - 'firefly-iii:fix-transaction-types', - 'firefly-iii:fix-frontpage-accounts', - 'firefly-iii:fix-ibans', - 'firefly-iii:create-group-memberships', - 'firefly-iii:upgrade-group-information', - - // two report commands - 'firefly-iii:report-empty-objects', - 'firefly-iii:report-sum', - 'firefly-iii:restore-oauth-keys', - - // instructions - 'firefly:instructions update', - 'firefly-iii:verify-security-alerts', + 'firefly-iii:budget-limit-periods', ]; $args = []; if ($this->option('force')) { @@ -117,9 +82,7 @@ class UpgradeDatabase extends Command } foreach ($commands as $command) { $this->line(sprintf('Now executing %s', $command)); - Artisan::call($command, $args); - $result = Artisan::output(); - echo $result; + $this->call($command, $args); } // set new DB version. app('fireflyconfig')->set('db_version', (int)config('firefly.db_version')); @@ -129,22 +92,19 @@ class UpgradeDatabase extends Command return 0; } + /** + * @return void + */ private function callInitialCommands(): void { $this->line('Now seeding the database...'); - Artisan::call('migrate', ['--seed' => true, '--force' => true]); - $result = Artisan::output(); - echo $result; + $this->call('migrate', ['--seed' => true, '--force' => true,'--no-interaction' => true]); $this->line('Fix PostgreSQL sequences.'); - Artisan::call('firefly-iii:fix-pgsql-sequences'); - $result = Artisan::output(); - echo $result; + $this->call('firefly-iii:fix-pgsql-sequences'); $this->line('Now decrypting the database (if necessary)...'); - Artisan::call('firefly-iii:decrypt-all'); - $result = Artisan::output(); - echo $result; + $this->call('firefly-iii:decrypt-all'); $this->line('Done!'); } diff --git a/app/Events/Model/BudgetLimit/Created.php b/app/Events/Model/BudgetLimit/Created.php new file mode 100644 index 0000000000..95b84f0523 --- /dev/null +++ b/app/Events/Model/BudgetLimit/Created.php @@ -0,0 +1,46 @@ +. + */ + +namespace FireflyIII\Events\Model\BudgetLimit; + +use FireflyIII\Events\Event; +use FireflyIII\Models\BudgetLimit; +use Illuminate\Queue\SerializesModels; + +/** + * Class Created + */ +class Created extends Event +{ + use SerializesModels; + + public BudgetLimit $budgetLimit; + + /** + * @param BudgetLimit $budgetLimit + */ + public function __construct(BudgetLimit $budgetLimit) + { + $this->budgetLimit = $budgetLimit; + } +} diff --git a/app/Events/Model/BudgetLimit/Deleted.php b/app/Events/Model/BudgetLimit/Deleted.php new file mode 100644 index 0000000000..9792d5c4e7 --- /dev/null +++ b/app/Events/Model/BudgetLimit/Deleted.php @@ -0,0 +1,46 @@ +. + */ + +namespace FireflyIII\Events\Model\BudgetLimit; + +use FireflyIII\Events\Event; +use FireflyIII\Models\BudgetLimit; +use Illuminate\Queue\SerializesModels; + +/** + * Class Deleted + */ +class Deleted extends Event +{ + use SerializesModels; + + public BudgetLimit $budgetLimit; + + /** + * @param BudgetLimit $budgetLimit + */ + public function __construct(BudgetLimit $budgetLimit) + { + $this->budgetLimit = $budgetLimit; + } +} diff --git a/app/Events/Model/BudgetLimit/Updated.php b/app/Events/Model/BudgetLimit/Updated.php new file mode 100644 index 0000000000..bbf36f3aae --- /dev/null +++ b/app/Events/Model/BudgetLimit/Updated.php @@ -0,0 +1,46 @@ +. + */ + +namespace FireflyIII\Events\Model\BudgetLimit; + +use FireflyIII\Events\Event; +use FireflyIII\Models\BudgetLimit; +use Illuminate\Queue\SerializesModels; + +/** + * Class Updated + */ +class Updated extends Event +{ + use SerializesModels; + + public BudgetLimit $budgetLimit; + + /** + * @param BudgetLimit $budgetLimit + */ + public function __construct(BudgetLimit $budgetLimit) + { + $this->budgetLimit = $budgetLimit; + } +} diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index 217cfa7933..f29a6f9391 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -122,7 +122,7 @@ class ChartJsGenerator implements GeneratorInterface foreach ($data as $set) { $currentSet = [ - 'label' => $set['label'], + 'label' => $set['label'] ?? '(no label)', 'type' => $set['type'] ?? 'line', 'data' => array_values($set['entries']), ]; diff --git a/app/Handlers/Events/Model/BudgetLimitHandler.php b/app/Handlers/Events/Model/BudgetLimitHandler.php new file mode 100644 index 0000000000..c097fcf7b5 --- /dev/null +++ b/app/Handlers/Events/Model/BudgetLimitHandler.php @@ -0,0 +1,218 @@ +. + */ + +namespace FireflyIII\Handlers\Events\Model; + +use FireflyIII\Events\Model\BudgetLimit\Created; +use FireflyIII\Events\Model\BudgetLimit\Deleted; +use FireflyIII\Events\Model\BudgetLimit\Updated; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; +use Illuminate\Support\Facades\Log; +use Spatie\Period\Boundaries; +use Spatie\Period\Period; +use Spatie\Period\Precision; + +/** + * Class BudgetLimitHandler + */ +class BudgetLimitHandler +{ + /** + * @param Created $event + * @return void + */ + public function created(Created $event): void + { + Log::debug(sprintf('BudgetLimitHandler::created(%s)', $event->budgetLimit->id)); + $this->updateAvailableBudget($event->budgetLimit); + } + + /** + * @param Updated $event + * @return void + */ + public function updated(Updated $event): void + { + Log::debug(sprintf('BudgetLimitHandler::updated(%s)', $event->budgetLimit->id)); + $this->updateAvailableBudget($event->budgetLimit); + } + + /** + * @param Deleted $event + * @return void + */ + public function deleted(Deleted $event): void + { + Log::debug(sprintf('BudgetLimitHandler::deleted(%s)', $event->budgetLimit->id)); + $this->updateAvailableBudget($event->budgetLimit); + } + + /** + * @param AvailableBudget $availableBudget + * @return void + */ + private function calculateAmount(AvailableBudget $availableBudget): void + { + $repository = app(BudgetLimitRepositoryInterface::class); + $repository->setUser($availableBudget->user); + $newAmount = '0'; + $abPeriod = Period::make($availableBudget->start_date, $availableBudget->end_date, Precision::DAY()); + Log::debug( + sprintf( + 'Now at AB #%d, ("%s" to "%s")', + $availableBudget->id, + $availableBudget->start_date->format('Y-m-d'), + $availableBudget->end_date->format('Y-m-d') + ) + ); + // have to recalc everything just in case. + $set = $repository->getAllBudgetLimitsByCurrency($availableBudget->transactionCurrency, $availableBudget->start_date, $availableBudget->end_date); + Log::debug(sprintf('Found %d interesting budget limit(s).', $set->count())); + /** @var BudgetLimit $budgetLimit */ + foreach ($set as $budgetLimit) { + Log::debug( + sprintf( + 'Found interesting budget limit #%d ("%s" to "%s")', + $budgetLimit->id, + $budgetLimit->start_date->format('Y-m-d'), + $budgetLimit->end_date->format('Y-m-d') + ) + ); + // overlap in days: + $limitPeriod = Period::make( + $budgetLimit->start_date, + $budgetLimit->end_date, + precision: Precision::DAY(), + boundaries: Boundaries::EXCLUDE_NONE() + ); + // if both equal eachother, amount from this BL must be added to the AB + if ($limitPeriod->equals($abPeriod)) { + $newAmount = bcadd($newAmount, $budgetLimit->amount); + } + // if budget limit period inside AB period, can be added in full. + if (!$limitPeriod->equals($abPeriod) && $abPeriod->contains($limitPeriod)) { + $newAmount = bcadd($newAmount, $budgetLimit->amount); + } + if (!$limitPeriod->equals($abPeriod) && $abPeriod->overlapsWith($limitPeriod)) { + $overlap = $abPeriod->overlap($limitPeriod); + if (null !== $overlap) { + $length = $overlap->length(); + $daily = bcmul($this->getDailyAmount($budgetLimit), (string)$length); + $newAmount = bcadd($newAmount, $daily); + } + } + } + Log::debug(sprintf('Concluded new amount for this AB must be %s', $newAmount)); + $availableBudget->amount = $newAmount; + $availableBudget->save(); + } + + /** + * @param BudgetLimit $budgetLimit + * @return string + */ + private function getDailyAmount(BudgetLimit $budgetLimit): string + { + $limitPeriod = Period::make( + $budgetLimit->start_date, + $budgetLimit->end_date, + precision: Precision::DAY(), + boundaries: Boundaries::EXCLUDE_NONE() + ); + $days = $limitPeriod->length(); + $amount = bcdiv((string)$budgetLimit->amount, (string)$days, 12); + Log::debug( + sprintf('Total amount for budget limit #%d is %s. Nr. of days is %d. Amount per day is %s', $budgetLimit->id, $budgetLimit->amount, $days, $amount) + ); + return $amount; + } + + /** + * @param BudgetLimit $budgetLimit + * @return void + * @throws \FireflyIII\Exceptions\FireflyException + * @throws \Psr\Container\ContainerExceptionInterface + * @throws \Psr\Container\NotFoundExceptionInterface + */ + private function updateAvailableBudget(BudgetLimit $budgetLimit): void + { + Log::debug(sprintf('Now in updateAvailableBudget(#%d)', $budgetLimit->id)); + + // based on the view range of the user (month week quarter etc) the budget limit could + // either overlap multiple available budget periods or be contained in a single one. + // all have to be created or updated. + $viewRange = app('preferences')->get('viewRange', '1M')->data; + $start = app('navigation')->startOfPeriod($budgetLimit->start_date, $viewRange); + $end = app('navigation')->startOfPeriod($budgetLimit->end_date, $viewRange); + $end = app('navigation')->endOfPeriod($end, $viewRange); + $user = $budgetLimit->budget->user; + + // limit period in total is: + $limitPeriod = Period::make($start, $end, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE()); + + // from the start until the end of the budget limit, need to loop! + $current = clone $start; + while ($current <= $end) { + $currentEnd = app('navigation')->endOfPeriod($current, $viewRange); + + // create or find AB for this particular period, and set the amount accordingly. + /** @var AvailableBudget $availableBudget */ + $availableBudget = $user->availableBudgets()->where('start_date', $current->format('Y-m-d'))->where( + 'end_date', + $currentEnd->format('Y-m-d') + )->where('transaction_currency_id', $budgetLimit->transaction_currency_id)->first(); + if (null !== $availableBudget) { + Log::debug('Found 1 AB, will update.'); + $this->calculateAmount($availableBudget); + } + if (null === $availableBudget) { + // if not exists: + $currentPeriod = Period::make($current, $currentEnd, precision: Precision::DAY(), boundaries: Boundaries::EXCLUDE_NONE()); + $daily = $this->getDailyAmount($budgetLimit); + $amount = bcmul($daily, (string)$currentPeriod->length(), 12); + + // no need to calculate if period is equal. + if ($currentPeriod->equals($limitPeriod)) { + $amount = $budgetLimit->amount; + } + Log::debug(sprintf('Will create AB for period %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d'))); + $availableBudget = new AvailableBudget( + [ + 'user_id' => $budgetLimit->budget->user->id, + 'transaction_currency_id' => $budgetLimit->transaction_currency_id, + 'start_date' => $current, + 'end_date' => $currentEnd, + 'amount' => $amount, + ] + ); + $availableBudget->save(); + } + + // prep for next loop + $current = app('navigation')->addPeriod($current, $viewRange, 0); + } + } + +} diff --git a/app/Http/Controllers/Budget/BudgetLimitController.php b/app/Http/Controllers/Budget/BudgetLimitController.php index b69b5e4c1d..b1d00e34e6 100644 --- a/app/Http/Controllers/Budget/BudgetLimitController.php +++ b/app/Http/Controllers/Budget/BudgetLimitController.php @@ -89,12 +89,13 @@ class BudgetLimitController extends Controller $collection = $this->currencyRepos->get(); $budgetLimits = $this->blRepository->getBudgetLimits($budget, $start, $end); - // remove already budgeted currencies: + // remove already budgeted currencies with the same date range $currencies = $collection->filter( - static function (TransactionCurrency $currency) use ($budgetLimits) { - /** @var AvailableBudget $budget */ - foreach ($budgetLimits as $budget) { - if ($budget->transaction_currency_id === $currency->id) { + static function (TransactionCurrency $currency) use ($budgetLimits, $start, $end) { + /** @var BudgetLimit $limit */ + foreach ($budgetLimits as $limit) { + if ($limit->transaction_currency_id === $currency->id && $limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end) + ) { return false; } } diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index aa6b6bd26d..5838305182 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -40,9 +40,9 @@ use Illuminate\Contracts\View\Factory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use JsonException; -use Illuminate\Support\Facades\Log; use Psr\Container\ContainerExceptionInterface; use Psr\Container\NotFoundExceptionInterface; @@ -101,12 +101,23 @@ class IndexController extends Controller */ public function index(Request $request, Carbon $start = null, Carbon $end = null) { - Log::debug('Start of IndexController::index()'); + Log::debug(sprintf('Start of IndexController::index("%s", "%s")', $start?->format('Y-m-d'), $end?->format('Y-m-d'))); // collect some basic vars: - $range = app('navigation')->getViewRange(true); - $start = $start ?? session('start', today(config('app.timezone'))->startOfMonth()); - $end = $end ?? app('navigation')->endOfPeriod($start, $range); + $range = app('navigation')->getViewRange(true); + $isCustomRange = session('is_custom_range', false); + if (false === $isCustomRange) { + $start = $start ?? session('start', today(config('app.timezone'))->startOfMonth()); + $end = $end ?? app('navigation')->endOfPeriod($start, $range); + } + + // overrule start and end if necessary: + if (true === $isCustomRange) { + $start = $start ?? session('start', today(config('app.timezone'))->startOfMonth()); + $end = $end ?? session('end', today(config('app.timezone'))->endOfMonth()); + } + + $defaultCurrency = app('amount')->getDefaultCurrency(); $currencies = $this->currencyRepository->get(); $budgeted = '0'; diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 62a5d7143a..72023adcd0 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -33,9 +33,9 @@ use FireflyIII\Support\Http\Controllers\GetConfigurationData; use Illuminate\Contracts\View\Factory; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Illuminate\Support\Facades\Log; use Illuminate\View\View; use Laravel\Passport\Passport; -use Illuminate\Support\Facades\Log; use phpseclib3\Crypt\RSA; /** @@ -60,61 +60,15 @@ class InstallController extends Controller { // empty on purpose. $this->upgradeCommands = [ - // there are 3 initial commands - 'migrate' => ['--seed' => true, '--force' => true], - 'firefly-iii:fix-pgsql-sequences' => [], - 'firefly-iii:decrypt-all' => [], - 'firefly-iii:restore-oauth-keys' => [], - 'generate-keys' => [], // an exception :( - - // upgrade commands - 'firefly-iii:transaction-identifiers' => [], - 'firefly-iii:migrate-to-groups' => [], - 'firefly-iii:account-currencies' => [], - 'firefly-iii:transfer-currencies' => [], - 'firefly-iii:other-currencies' => [], - 'firefly-iii:migrate-notes' => [], - 'firefly-iii:migrate-attachments' => [], - 'firefly-iii:bills-to-rules' => [], - 'firefly-iii:bl-currency' => [], - 'firefly-iii:cc-liabilities' => [], - 'firefly-iii:back-to-journals' => [], - 'firefly-iii:rename-account-meta' => [], - 'firefly-iii:migrate-recurrence-meta' => [], - 'firefly-iii:migrate-tag-locations' => [], - 'firefly-iii:migrate-recurrence-type' => [], - 'firefly-iii:upgrade-liabilities' => [], - 'firefly-iii:liabilities-600' => [], - - // verify commands - 'firefly-iii:fix-piggies' => [], - 'firefly-iii:create-link-types' => [], - 'firefly-iii:create-access-tokens' => [], - 'firefly-iii:remove-bills' => [], - 'firefly-iii:fix-negative-limits' => [], - 'firefly-iii:enable-currencies' => [], - 'firefly-iii:fix-transfer-budgets' => [], - 'firefly-iii:fix-uneven-amount' => [], - 'firefly-iii:delete-zero-amount' => [], - 'firefly-iii:delete-orphaned-transactions' => [], - 'firefly-iii:delete-empty-journals' => [], - 'firefly-iii:delete-empty-groups' => [], - 'firefly-iii:fix-account-types' => [], - 'firefly-iii:fix-account-order' => [], - 'firefly-iii:rename-meta-fields' => [], - 'firefly-iii:fix-ob-currencies' => [], - 'firefly-iii:fix-long-descriptions' => [], - 'firefly-iii:fix-recurring-transactions' => [], - 'firefly-iii:unify-group-accounts' => [], - 'firefly-iii:fix-transaction-types' => [], - 'firefly-iii:fix-frontpage-accounts' => [], - 'firefly-iii:fix-ibans' => [], - 'firefly-iii:create-group-memberships' => [], - 'firefly-iii:upgrade-group-information' => [], - - // final command to set the latest version in DB - 'firefly-iii:set-latest-version' => ['--james-is-cool' => true], - 'firefly-iii:verify-security-alerts' => [], + // there are 5 initial commands + // Check 4 places: InstallController, Docker image, UpgradeDatabase, composer.json + 'migrate' => ['--seed' => true, '--force' => true], + 'generate-keys' => [], // an exception :( + 'firefly-iii:upgrade-database' => [], + 'firefly-iii:correct-database' => [], + 'firefly-iii:report-integrity' => [], + 'firefly-iii:set-latest-version' => ['--james-is-cool' => true], + 'firefly-iii:verify-security-alerts' => [], ]; $this->lastError = ''; @@ -155,8 +109,8 @@ class InstallController extends Controller Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); $indexes = array_values(array_keys($this->upgradeCommands)); - if(array_key_exists($requestIndex, $indexes)) { - $command = $indexes[$requestIndex]; + if (array_key_exists($requestIndex, $indexes)) { + $command = $indexes[$requestIndex]; $parameters = $this->upgradeCommands[$command]; Log::debug(sprintf('Will now execute command "%s" with parameters', $command), $parameters); try { diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 584ee3d018..68794f1421 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -87,13 +87,10 @@ class Authenticate */ protected function authenticate($request, array $guards) { - Log::debug(sprintf('Now in %s', __METHOD__)); if (0 === count($guards)) { - Log::debug('No guards present.'); // go for default guard: /** @noinspection PhpUndefinedMethodInspection */ if ($this->auth->check()) { - Log::debug('Default guard says user is authenticated.'); // do an extra check on user object. /** @noinspection PhpUndefinedMethodInspection */ /** @var User $user */ @@ -104,18 +101,13 @@ class Authenticate /** @noinspection PhpUndefinedMethodInspection */ return $this->auth->authenticate(); } - Log::debug('Guard array is not empty.'); foreach ($guards as $guard) { - Log::debug(sprintf('Now in guard loop, guard is "%s"', $guard)); if ('api' !== $guard) { - Log::debug('Guard is "api", call authenticate()'); $this->auth->guard($guard)->authenticate(); } $result = $this->auth->guard($guard)->check(); - Log::debug(sprintf('Result is %s', var_export($result, true))); if ($result) { - Log::debug('Guard says user is authenticated.'); $user = $this->auth->guard($guard)->user(); $this->validateBlockedUser($user, $guards); // According to PHPstan the method returns void, but we'll see. @@ -134,7 +126,6 @@ class Authenticate */ private function validateBlockedUser(?User $user, array $guards): void { - Log::debug(sprintf('Now in %s', __METHOD__)); if (null === $user) { Log::warning('User is null, throw exception?'); } diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 6a9a132641..2c5ea2818c 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -110,7 +110,7 @@ class AccountFormRequest extends FormRequest $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $rules = [ 'administration_id' => 'min:1|max:16777216|numeric', - 'name' => 'required|min:1|uniqueAccountForUser', + 'name' => 'required|max:1024|min:1|uniqueAccountForUser', 'opening_balance' => 'numeric|nullable|max:1000000000', 'opening_balance_date' => 'date|required_with:opening_balance|nullable', 'iban' => ['iban', 'nullable', new UniqueIban(null, $this->convertString('objectType'))], @@ -133,7 +133,7 @@ class AccountFormRequest extends FormRequest if (null !== $account) { // add rules: $rules['id'] = 'belongsToUser:accounts'; - $rules['name'] = 'required|min:1|uniqueAccountForUser:'.$account->id; + $rules['name'] = 'required|max:1024|min:1|uniqueAccountForUser:'.$account->id; $rules['iban'] = ['iban', 'nullable', new UniqueIban($account, $account->accountType->type)]; } diff --git a/app/Http/Requests/BillUpdateRequest.php b/app/Http/Requests/BillUpdateRequest.php index d205a6d680..89ffa5b0a7 100644 --- a/app/Http/Requests/BillUpdateRequest.php +++ b/app/Http/Requests/BillUpdateRequest.php @@ -81,6 +81,7 @@ class BillUpdateRequest extends FormRequest 'repeat_freq' => sprintf('required|in:%s', join(',', config('firefly.bill_periods'))), 'skip' => 'required|integer|gte:0|lte:31', 'active' => 'boolean', + 'notes' => 'between:1,65536|nullable', ]; } } diff --git a/app/Http/Requests/LinkTypeFormRequest.php b/app/Http/Requests/LinkTypeFormRequest.php index d8864ba15e..2705775ba7 100644 --- a/app/Http/Requests/LinkTypeFormRequest.php +++ b/app/Http/Requests/LinkTypeFormRequest.php @@ -43,7 +43,7 @@ class LinkTypeFormRequest extends FormRequest public function rules(): array { // fixed - $nameRule = 'required|min:1|unique:link_types,name'; + $nameRule = 'required|max:255|min:1|unique:link_types,name'; $idRule = ''; // get parameter link: @@ -51,14 +51,14 @@ class LinkTypeFormRequest extends FormRequest if (null !== $link) { $idRule = 'exists:link_types,id'; - $nameRule = 'required|min:1'; + $nameRule = 'required|max:255|min:1'; } return [ 'id' => $idRule, 'name' => $nameRule, - 'inward' => 'required|min:1|different:outward', - 'outward' => 'required|min:1|different:inward', + 'inward' => 'required|max:255|min:1|different:outward', + 'outward' => 'required|max:255|min:1|different:inward', ]; } } diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index d7759af6a1..0ac30468b3 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -45,7 +45,7 @@ class MassEditJournalRequest extends FormRequest // fixed return [ - 'description.*' => 'required|min:1,max:255', + 'description.*' => 'required|min:1|max:255', 'source_id.*' => 'numeric|belongsToUser:accounts,id', 'destination_id.*' => 'numeric|belongsToUser:accounts,id', 'journals.*' => 'numeric|belongsToUser:transaction_journals,id', diff --git a/app/Http/Requests/PiggyBankUpdateRequest.php b/app/Http/Requests/PiggyBankUpdateRequest.php index 7df2c58629..4f7558689c 100644 --- a/app/Http/Requests/PiggyBankUpdateRequest.php +++ b/app/Http/Requests/PiggyBankUpdateRequest.php @@ -70,7 +70,7 @@ class PiggyBankUpdateRequest extends FormRequest 'targetamount' => 'nullable|numeric|max:1000000000', 'startdate' => 'date', 'targetdate' => 'date|nullable', - 'order' => 'integer|min:1', + 'order' => 'integer|max:65536|min:1', 'object_group' => 'min:0|max:255', ]; } diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 152f9da4d3..facf6ceb63 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -157,9 +157,9 @@ class RuleFormRequest extends FormRequest 'rule_group_id' => 'required|belongsToUser:rule_groups', 'trigger' => 'required|in:store-journal,update-journal', 'triggers.*.type' => 'required|in:'.implode(',', $validTriggers), - 'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|min:1|ruleTriggerValue', $contextTriggers), + 'triggers.*.value' => sprintf('required_if:triggers.*.type,%s|max:1024|min:1|ruleTriggerValue', $contextTriggers), 'actions.*.type' => 'required|in:'.implode(',', $validActions), - 'actions.*.value' => sprintf('required_if:actions.*.type,%s|min:0|max:255|ruleActionValue', $contextActions), + 'actions.*.value' => sprintf('required_if:actions.*.type,%s|min:0|max:1024|ruleActionValue', $contextActions), 'strict' => 'in:0,1', ]; diff --git a/app/Http/Requests/TagFormRequest.php b/app/Http/Requests/TagFormRequest.php index 25adcd9450..9a4f82fdad 100644 --- a/app/Http/Requests/TagFormRequest.php +++ b/app/Http/Requests/TagFormRequest.php @@ -66,17 +66,18 @@ class TagFormRequest extends FormRequest /** @var Tag $tag */ $tag = $this->route()->parameter('tag'); - $tagRule = 'required|min:1|uniqueObjectForUser:tags,tag'; + $tagRule = 'required|max:1024|min:1|uniqueObjectForUser:tags,tag'; if (null !== $tag) { $idRule = 'belongsToUser:tags'; - $tagRule = 'required|min:1|uniqueObjectForUser:tags,tag,'.$tag->id; + $tagRule = 'required|max:1024|min:1|uniqueObjectForUser:tags,tag,'.$tag->id; } $rules = [ 'tag' => $tagRule, 'id' => $idRule, - 'description' => 'min:1|nullable', + 'description' => 'max:65536|min:1|nullable', 'date' => 'date|nullable', + ]; return Location::requestRules($rules); diff --git a/app/Http/Requests/TestRuleFormRequest.php b/app/Http/Requests/TestRuleFormRequest.php index f2afa826e5..261f64437f 100644 --- a/app/Http/Requests/TestRuleFormRequest.php +++ b/app/Http/Requests/TestRuleFormRequest.php @@ -49,8 +49,8 @@ class TestRuleFormRequest extends FormRequest $validTriggers = $this->getTriggers(); return [ - 'rule-trigger.*' => 'required|min:1|in:'.implode(',', $validTriggers), - 'rule-trigger-value.*' => 'required|min:1|ruleTriggerValue', + 'rule-trigger.*' => 'required|max:1024|min:1|in:'.implode(',', $validTriggers), + 'rule-trigger-value.*' => 'required|max:1024|min:1|ruleTriggerValue', ]; } } diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index b8419b0fce..f1e1c30775 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -24,6 +24,9 @@ declare(strict_types=1); namespace FireflyIII\Models; use Eloquent; +use FireflyIII\Events\Model\BudgetLimit\Created; +use FireflyIII\Events\Model\BudgetLimit\Deleted; +use FireflyIII\Events\Model\BudgetLimit\Updated; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; @@ -81,6 +84,12 @@ class BudgetLimit extends Model /** @var array Fields that can be filled */ protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount', 'transaction_currency_id']; + protected $dispatchesEvents = [ + 'created' => Created::class, + 'updated' => Updated::class, + 'deleted' => Deleted::class, + ]; + /** * Route binder. Converts the key in the URL to the specified object (or throw 404). * diff --git a/app/Models/UserGroup.php b/app/Models/UserGroup.php index 5406d86c0b..2efe78a40b 100644 --- a/app/Models/UserGroup.php +++ b/app/Models/UserGroup.php @@ -53,6 +53,7 @@ use Illuminate\Support\Carbon; * @property-read int|null $accounts_count * @property-read Collection $accounts * @property-read Collection $accounts + * @property-read Collection $accounts * @mixin Eloquent */ class UserGroup extends Model diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 5588d32550..608cf20806 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -29,6 +29,9 @@ use FireflyIII\Events\AdminRequestedTestMessage; use FireflyIII\Events\ChangedPiggyBankAmount; use FireflyIII\Events\DestroyedTransactionGroup; use FireflyIII\Events\DetectedNewIPAddress; +use FireflyIII\Events\Model\BudgetLimit\Created; +use FireflyIII\Events\Model\BudgetLimit\Deleted; +use FireflyIII\Events\Model\BudgetLimit\Updated; use FireflyIII\Events\NewVersionAvailable; use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; @@ -42,6 +45,7 @@ use FireflyIII\Events\UpdatedAccount; use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Events\UserChangedEmail; use FireflyIII\Events\WarnUserAboutBill; +use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\PiggyBankRepetition; @@ -160,6 +164,17 @@ class EventServiceProvider extends ServiceProvider ChangedPiggyBankAmount::class => [ 'FireflyIII\Handlers\Events\PiggyBankEventHandler@changePiggyAmount', ], + // budget related events: CRUD budget limit + Created::class => [ + 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@created', + ], + Updated::class => [ + 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@updated', + ], + Deleted::class => [ + 'FireflyIII\Handlers\Events\Model\BudgetLimitHandler@deleted', + ], + ]; /** @@ -169,7 +184,6 @@ class EventServiceProvider extends ServiceProvider { parent::boot(); $this->registerCreateEvents(); - $this->registerBudgetEvents(); } /** @@ -188,57 +202,4 @@ class EventServiceProvider extends ServiceProvider } ); } - - /** - * TODO needs a dedicated method. - */ - protected function registerBudgetEvents(): void - { - $func = static function (BudgetLimit $limit) { - Log::debug('Trigger budget limit event.'); - // find available budget with same period and same currency or create it. - // then set it or add money: - $user = $limit->budget->user; - $availableBudget = $user - ->availableBudgets() - ->where('start_date', $limit->start_date->format('Y-m-d')) - ->where('end_date', $limit->end_date->format('Y-m-d')) - ->where('transaction_currency_id', $limit->transaction_currency_id) - ->first(); - // update! - if (null !== $availableBudget) { - $repository = app(BudgetLimitRepositoryInterface::class); - $repository->setUser($user); - $set = $repository->getAllBudgetLimitsByCurrency($limit->transactionCurrency, $limit->start_date, $limit->end_date); - $sum = (string)$set->sum('amount'); - - - Log::debug( - sprintf( - 'Because budget limit #%d had its amount changed to %s, available budget limit #%d will be updated.', - $limit->id, - $limit->amount, - $availableBudget->id - ) - ); - $availableBudget->amount = $sum; - $availableBudget->save(); - return; - } - Log::debug('Does not exist, create it.'); - // create it. - $data = [ - 'amount' => $limit->amount, - 'start' => $limit->start_date, - 'end' => $limit->end_date, - 'currency_id' => $limit->transaction_currency_id, - ]; - $repository = app(AvailableBudgetRepositoryInterface::class); - $repository->setUser($user); - $repository->store($data); - }; - - BudgetLimit::created($func); - BudgetLimit::updated($func); - } } diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 929057593e..659550186e 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -856,12 +856,12 @@ class OperatorQuerySearch implements SearchInterface break; case '-tag_is_not': case 'tag_is': - $result = $this->tagRepository->searchTag($value); - if ($result->count() > 0) { - $this->collector->setTags($result); + $result = $this->tagRepository->findByTag($value); + if (null !== $result) { + $this->collector->setTags(new Collection([$result])); } // no tags found means search must result in nothing. - if (0 === $result->count()) { + if (null === $result) { Log::info(sprintf('No valid tags in "%s"-operator, so search will not return ANY results.', $operator)); $this->collector->findNothing(); } diff --git a/app/Validation/GroupValidation.php b/app/Validation/GroupValidation.php index b713fd8150..78d18a55aa 100644 --- a/app/Validation/GroupValidation.php +++ b/app/Validation/GroupValidation.php @@ -24,6 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Validation; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\TransactionGroup; use Illuminate\Validation\Validator; use Illuminate\Support\Facades\Log; @@ -53,6 +54,9 @@ trait GroupValidation ]; /** @var array $transaction */ foreach ($transactions as $index => $transaction) { + if(!is_array($transaction)) { + throw new FireflyException('Invalid data submitted: transaction is not array.'); + } $hasAccountInfo = false; $hasJournalId = array_key_exists('transaction_journal_id', $transaction); foreach ($keys as $key) { diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 1f4bc0db31..e246a6171c 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Validation; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; @@ -89,6 +90,7 @@ trait TransactionValidation Log::error(sprintf('Transactions array is not countable, because its a %s', gettype($transactions))); return []; } + Log::debug('Returning transactions.', $transactions); return $transactions; } @@ -357,6 +359,9 @@ trait TransactionValidation * @var array $transaction */ foreach ($transactions as $index => $transaction) { + if(!is_int($index)) { + throw new FireflyException('Invalid data submitted: transaction is not array.'); + } $this->validateSingleUpdate($validator, $index, $transaction, $transactionGroup); } } diff --git a/composer.json b/composer.json index 529c7b5b3e..4013e58c6d 100644 --- a/composer.json +++ b/composer.json @@ -82,7 +82,7 @@ "ext-xml": "*", "ext-xmlwriter": "*", "bacon/bacon-qr-code": "2.*", - "diglactic/laravel-breadcrumbs": "^8.0", + "diglactic/laravel-breadcrumbs": "^8.1", "doctrine/dbal": "3.*", "gdbots/query-parser": "^3.0", "guzzlehttp/guzzle": "^7.5", @@ -104,6 +104,7 @@ "ramsey/uuid": "^4.7", "rcrowe/twigbridge": "^0.14", "spatie/laravel-ignition": "^2", + "spatie/period": "^2.4", "symfony/http-client": "^6.0", "symfony/mailgun-mailer": "^6.0", "therobfonz/laravel-mandrill-driver": "^5.0" @@ -114,7 +115,7 @@ "fakerphp/faker": "1.*", "filp/whoops": "2.*", "mockery/mockery": "1.*", - "nunomaduro/larastan": "^2.5", + "nunomaduro/larastan": "^2.6", "phpstan/phpstan": "^1.10", "phpstan/phpstan-deprecation-rules": "^1.1", "phpstan/phpstan-strict-rules": "^1.4", @@ -155,57 +156,17 @@ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" ], "post-update-cmd": [ + "@php artisan config:clear", + "@php artisan route:clear", + "@php artisan twig:clean", + "@php artisan view:clear", + "@php artisan clear-compiled", "@php artisan cache:clear", - "@php artisan firefly-iii:fix-pgsql-sequences", - "@php artisan firefly-iii:decrypt-all", - "@php artisan firefly-iii:transaction-identifiers", - "@php artisan firefly-iii:migrate-to-groups", - "@php artisan firefly-iii:account-currencies", - "@php artisan firefly-iii:transfer-currencies", - "@php artisan firefly-iii:other-currencies", - "@php artisan firefly-iii:migrate-notes", - "@php artisan firefly-iii:migrate-attachments", - "@php artisan firefly-iii:bills-to-rules", - "@php artisan firefly-iii:bl-currency", - "@php artisan firefly-iii:cc-liabilities", - "@php artisan firefly-iii:back-to-journals", - "@php artisan firefly-iii:rename-account-meta", - "@php artisan firefly-iii:migrate-recurrence-meta", - "@php artisan firefly-iii:migrate-tag-locations", - "@php artisan firefly-iii:migrate-recurrence-type", - "@php artisan firefly-iii:upgrade-liabilities", - "@php artisan firefly-iii:liabilities-600", - "@php artisan firefly-iii:fix-piggies", - "@php artisan firefly-iii:create-link-types", - "@php artisan firefly-iii:create-access-tokens", - "@php artisan firefly-iii:remove-bills", - "@php artisan firefly-iii:fix-negative-limits", - "@php artisan firefly-iii:enable-currencies", - "@php artisan firefly-iii:fix-transfer-budgets", - "@php artisan firefly-iii:fix-uneven-amount", - "@php artisan firefly-iii:delete-zero-amount", - "@php artisan firefly-iii:delete-orphaned-transactions", - "@php artisan firefly-iii:delete-empty-journals", - "@php artisan firefly-iii:delete-empty-groups", - "@php artisan firefly-iii:fix-account-types", - "@php artisan firefly-iii:fix-account-order", - "@php artisan firefly-iii:rename-meta-fields", - "@php artisan firefly-iii:fix-ob-currencies", - "@php artisan firefly-iii:fix-long-descriptions", - "@php artisan firefly-iii:fix-recurring-transactions", - "@php artisan firefly-iii:unify-group-accounts", - "@php artisan firefly-iii:fix-transaction-types", - "@php artisan firefly-iii:fix-frontpage-accounts", - "@php artisan firefly-iii:fix-ibans", - "@php artisan firefly-iii:create-group-memberships", - "@php artisan firefly-iii:report-empty-objects", - "@php artisan firefly-iii:report-sum", - "@php artisan firefly-iii:restore-oauth-keys", - "@php artisan firefly-iii:upgrade-group-information", - "@php artisan firefly-iii:set-latest-version --james-is-cool", - "@php artisan firefly:instructions update", - "@php artisan firefly-iii:verify-security-alerts", - "@php artisan passport:install" + "@php artisan firefly-iii:upgrade-database", + "@php artisan firefly-iii:correct-database", + "@php artisan firefly-iii:report-integrity", + "@php artisan passport:install", + "@php artisan firefly:instructions update" ], "post-install-cmd": [ "@php artisan firefly:instructions install", diff --git a/composer.lock b/composer.lock index a8a9f8edc1..383e647ac7 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ac90c0382ca1d0cb597ee1418a066866", + "content-hash": "3d09d838fdf529c07df3563a3d96de5c", "packages": [ { "name": "bacon/bacon-qr-code", @@ -62,26 +62,25 @@ }, { "name": "brick/math", - "version": "0.10.2", + "version": "0.11.0", "source": { "type": "git", "url": "https://github.com/brick/math.git", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f" + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/brick/math/zipball/459f2781e1a08d52ee56b0b1444086e038561e3f", - "reference": "459f2781e1a08d52ee56b0b1444086e038561e3f", + "url": "https://api.github.com/repos/brick/math/zipball/0ad82ce168c82ba30d1c01ec86116ab52f589478", + "reference": "0ad82ce168c82ba30d1c01ec86116ab52f589478", "shasum": "" }, "require": { - "ext-json": "*", - "php": "^7.4 || ^8.0" + "php": "^8.0" }, "require-dev": { "php-coveralls/php-coveralls": "^2.2", "phpunit/phpunit": "^9.0", - "vimeo/psalm": "4.25.0" + "vimeo/psalm": "5.0.0" }, "type": "library", "autoload": { @@ -106,7 +105,7 @@ ], "support": { "issues": "https://github.com/brick/math/issues", - "source": "https://github.com/brick/math/tree/0.10.2" + "source": "https://github.com/brick/math/tree/0.11.0" }, "funding": [ { @@ -114,7 +113,7 @@ "type": "github" } ], - "time": "2022-08-10T22:54:19+00:00" + "time": "2023-01-15T23:15:59+00:00" }, { "name": "dasprid/enum", @@ -309,16 +308,16 @@ }, { "name": "diglactic/laravel-breadcrumbs", - "version": "v8.1.0", + "version": "v8.1.1", "source": { "type": "git", "url": "https://github.com/diglactic/laravel-breadcrumbs.git", - "reference": "ce3dfb760743c63a287dab4b8090d7bf68b321ee" + "reference": "f72a78eb3e26aea507d7888a65f15e5790864e21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/diglactic/laravel-breadcrumbs/zipball/ce3dfb760743c63a287dab4b8090d7bf68b321ee", - "reference": "ce3dfb760743c63a287dab4b8090d7bf68b321ee", + "url": "https://api.github.com/repos/diglactic/laravel-breadcrumbs/zipball/f72a78eb3e26aea507d7888a65f15e5790864e21", + "reference": "f72a78eb3e26aea507d7888a65f15e5790864e21", "shasum": "" }, "require": { @@ -374,9 +373,9 @@ ], "support": { "issues": "https://github.com/diglactic/laravel-breadcrumbs/issues", - "source": "https://github.com/diglactic/laravel-breadcrumbs/tree/v8.1.0" + "source": "https://github.com/diglactic/laravel-breadcrumbs/tree/v8.1.1" }, - "time": "2023-02-06T22:46:35+00:00" + "time": "2023-04-17T23:24:15+00:00" }, { "name": "doctrine/cache", @@ -1374,22 +1373,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.5.0", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba" + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba", - "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b964ca597e86b752cd994f27293e9fa6b6a95ed9", + "reference": "b964ca597e86b752cd994f27293e9fa6b6a95ed9", "shasum": "" }, "require": { "ext-json": "*", "guzzlehttp/promises": "^1.5", - "guzzlehttp/psr7": "^1.9 || ^2.4", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -1482,7 +1481,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.5.0" + "source": "https://github.com/guzzle/guzzle/tree/7.5.1" }, "funding": [ { @@ -1498,7 +1497,7 @@ "type": "tidelift" } ], - "time": "2022-08-28T15:39:27+00:00" + "time": "2023-04-17T16:30:08+00:00" }, { "name": "guzzlehttp/promises", @@ -1941,16 +1940,16 @@ }, { "name": "laravel/framework", - "version": "v10.7.1", + "version": "v10.8.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "ddbbb2b50388721fe63312bb4469cae13163fd36" + "reference": "317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/ddbbb2b50388721fe63312bb4469cae13163fd36", - "reference": "ddbbb2b50388721fe63312bb4469cae13163fd36", + "url": "https://api.github.com/repos/laravel/framework/zipball/317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8", + "reference": "317d7ccaeb1bbf4ac3035efe225ef2746c45f3a8", "shasum": "" }, "require": { @@ -2137,7 +2136,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-04-11T14:11:49+00:00" + "time": "2023-04-18T13:45:33+00:00" }, { "name": "laravel/passport", @@ -5335,20 +5334,20 @@ }, { "name": "ramsey/uuid", - "version": "4.7.3", + "version": "4.7.4", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2" + "reference": "60a4c63ab724854332900504274f6150ff26d286" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/433b2014e3979047db08a17a205f410ba3869cf2", - "reference": "433b2014e3979047db08a17a205f410ba3869cf2", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/60a4c63ab724854332900504274f6150ff26d286", + "reference": "60a4c63ab724854332900504274f6150ff26d286", "shasum": "" }, "require": { - "brick/math": "^0.8.8 || ^0.9 || ^0.10", + "brick/math": "^0.8.8 || ^0.9 || ^0.10 || ^0.11", "ext-json": "*", "php": "^8.0", "ramsey/collection": "^1.2 || ^2.0" @@ -5411,7 +5410,7 @@ ], "support": { "issues": "https://github.com/ramsey/uuid/issues", - "source": "https://github.com/ramsey/uuid/tree/4.7.3" + "source": "https://github.com/ramsey/uuid/tree/4.7.4" }, "funding": [ { @@ -5423,7 +5422,7 @@ "type": "tidelift" } ], - "time": "2023-01-12T18:13:24+00:00" + "time": "2023-04-15T23:01:58+00:00" }, { "name": "rcrowe/twigbridge", @@ -5807,6 +5806,60 @@ ], "time": "2023-04-12T09:26:00+00:00" }, + { + "name": "spatie/period", + "version": "2.4.0", + "source": { + "type": "git", + "url": "https://github.com/spatie/period.git", + "reference": "85fbbea7b24fdff0c924aeed5b109be93c025850" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/spatie/period/zipball/85fbbea7b24fdff0c924aeed5b109be93c025850", + "reference": "85fbbea7b24fdff0c924aeed5b109be93c025850", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "larapack/dd": "^1.1", + "nesbot/carbon": "^2.63", + "pestphp/pest": "^1.22", + "phpunit/phpunit": "^9.5", + "spatie/ray": "^1.31" + }, + "type": "library", + "autoload": { + "psr-4": { + "Spatie\\Period\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brent Roose", + "email": "brent@spatie.be", + "homepage": "https://spatie.be", + "role": "Developer" + } + ], + "description": "Complex period comparisons", + "homepage": "https://github.com/spatie/period", + "keywords": [ + "period", + "spatie" + ], + "support": { + "issues": "https://github.com/spatie/period/issues", + "source": "https://github.com/spatie/period/tree/2.4.0" + }, + "time": "2023-02-20T14:31:09+00:00" + }, { "name": "symfony/console", "version": "v6.2.8", @@ -9409,16 +9462,16 @@ }, { "name": "nunomaduro/larastan", - "version": "2.5.1", + "version": "v2.6.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/larastan.git", - "reference": "072e2c9566ae000bf66c92384fc933b81885244b" + "reference": "ccac5b25949576807862cf32ba1fce1769c06c42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/072e2c9566ae000bf66c92384fc933b81885244b", - "reference": "072e2c9566ae000bf66c92384fc933b81885244b", + "url": "https://api.github.com/repos/nunomaduro/larastan/zipball/ccac5b25949576807862cf32ba1fce1769c06c42", + "reference": "ccac5b25949576807862cf32ba1fce1769c06c42", "shasum": "" }, "require": { @@ -9432,7 +9485,7 @@ "illuminate/support": "^9.47.0 || ^10.0.0", "php": "^8.0.2", "phpmyadmin/sql-parser": "^5.6.0", - "phpstan/phpstan": "~1.10.3" + "phpstan/phpstan": "~1.10.6" }, "require-dev": { "nikic/php-parser": "^4.15.2", @@ -9481,7 +9534,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/larastan/issues", - "source": "https://github.com/nunomaduro/larastan/tree/2.5.1" + "source": "https://github.com/nunomaduro/larastan/tree/v2.6.0" }, "funding": [ { @@ -9501,7 +9554,7 @@ "type": "patreon" } ], - "time": "2023-03-04T23:46:40+00:00" + "time": "2023-04-20T12:40:01+00:00" }, { "name": "phar-io/manifest", @@ -9859,16 +9912,16 @@ }, { "name": "phpstan/phpstan", - "version": "1.10.13", + "version": "1.10.14", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70" + "reference": "d232901b09e67538e5c86a724be841bea5768a7c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/f07bf8c6980b81bf9e49d44bd0caf2e737614a70", - "reference": "f07bf8c6980b81bf9e49d44bd0caf2e737614a70", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d232901b09e67538e5c86a724be841bea5768a7c", + "reference": "d232901b09e67538e5c86a724be841bea5768a7c", "shasum": "" }, "require": { @@ -9917,7 +9970,7 @@ "type": "tidelift" } ], - "time": "2023-04-12T19:29:52+00:00" + "time": "2023-04-19T13:47:27+00:00" }, { "name": "phpstan/phpstan-deprecation-rules", diff --git a/package.json b/package.json index 2292bce4a9..089daeb010 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "devDependencies": { "@johmun/vue-tags-input": "^2", "@vue/compiler-sfc": "^3.2.45", - "axios": "^1.2", + "axios": "^1.3", "bootstrap-sass": "^3", "cross-env": "^7.0", "font-awesome": "^4.7.0", diff --git a/public/v1/css/daterangepicker-dark.css b/public/v1/css/daterangepicker-dark.css new file mode 100644 index 0000000000..bc15d7f245 --- /dev/null +++ b/public/v1/css/daterangepicker-dark.css @@ -0,0 +1,393 @@ + /** + fff = 282d32 + eee = 31373e + ddd = 3f4750 + ebf4f8 = 4b4f50 + */ + .daterangepicker { + position: absolute; + color: inherit; + background-color: #282d32; + border-radius: 4px; + border: 1px solid #3f4750; + width: 278px; + max-width: none; + padding: 0; + margin-top: 7px; + top: 100px; + left: 20px; + z-index: 3001; + display: none; + font-family: sans-serif, Arial; + font-size: 15px; + line-height: 1em; + } + .daterangepicker .calendar-table { + border: 1px solid #282d32; + border-radius: 4px; + background-color: #282d32; + } + .daterangepicker td.available:hover, .daterangepicker th.available:hover { + background-color: #31373e; + border-color: transparent; + color: inherit; + } + .daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date { + background-color: #282d32; + border-color: transparent; + color: #999; + } + + .daterangepicker td.in-range { + background-color: #4b4f50; + border-color: transparent; + color: #000; + border-radius: 0; + } + +.daterangepicker:before, .daterangepicker:after { + position: absolute; + display: inline-block; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker:before { + top: -7px; + border-right: 7px solid transparent; + border-left: 7px solid transparent; + border-bottom: 7px solid #ccc; +} + +.daterangepicker:after { + top: -6px; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; +} + +.daterangepicker.opensleft:before { + right: 9px; +} + +.daterangepicker.opensleft:after { + right: 10px; +} + +.daterangepicker.openscenter:before { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.openscenter:after { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.opensright:before { + left: 9px; +} + +.daterangepicker.opensright:after { + left: 10px; +} + +.daterangepicker.drop-up { + margin-top: -7px; +} + +.daterangepicker.drop-up:before { + top: initial; + bottom: -7px; + border-bottom: initial; + border-top: 7px solid #ccc; +} + +.daterangepicker.drop-up:after { + top: initial; + bottom: -6px; + border-bottom: initial; + border-top: 6px solid #fff; +} + +.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar { + float: none; +} + +.daterangepicker.single .drp-selected { + display: none; +} + +.daterangepicker.show-calendar .drp-calendar { + display: block; +} + +.daterangepicker.show-calendar .drp-buttons { + display: block; +} + +.daterangepicker.auto-apply .drp-buttons { + display: none; +} + +.daterangepicker .drp-calendar { + display: none; + max-width: 270px; +} + +.daterangepicker .drp-calendar.left { + padding: 8px 0 8px 8px; +} + +.daterangepicker .drp-calendar.right { + padding: 8px; +} + +.daterangepicker .drp-calendar.single .calendar-table { + border: none; +} + +.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span { + color: #fff; + border: solid black; + border-width: 0 2px 2px 0; + border-radius: 0; + display: inline-block; + padding: 3px; +} + +.daterangepicker .calendar-table .next span { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.daterangepicker .calendar-table .prev span { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.daterangepicker .calendar-table th, .daterangepicker .calendar-table td { + white-space: nowrap; + text-align: center; + vertical-align: middle; + min-width: 32px; + width: 32px; + height: 24px; + line-height: 24px; + font-size: 12px; + border-radius: 4px; + border: 1px solid transparent; + cursor: pointer; +} + +.daterangepicker .calendar-table table { + width: 100%; + margin: 0; + border-spacing: 0; + border-collapse: collapse; +} + + + +.daterangepicker td.week, .daterangepicker th.week { + font-size: 80%; + color: #ccc; +} + + +.daterangepicker td.start-date { + border-radius: 4px 0 0 4px; +} + +.daterangepicker td.end-date { + border-radius: 0 4px 4px 0; +} + +.daterangepicker td.start-date.end-date { + border-radius: 4px; +} + +.daterangepicker td.active, .daterangepicker td.active:hover { + background-color: #357ebd; + border-color: transparent; + color: #fff; +} + +.daterangepicker th.month { + width: auto; +} + +.daterangepicker td.disabled, .daterangepicker option.disabled { + color: #999; + cursor: not-allowed; + text-decoration: line-through; +} + +.daterangepicker select.monthselect, .daterangepicker select.yearselect { + font-size: 12px; + padding: 1px; + height: auto; + margin: 0; + cursor: default; +} + +.daterangepicker select.monthselect { + margin-right: 2%; + width: 56%; +} + +.daterangepicker select.yearselect { + width: 40%; +} + +.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { + width: 50px; + margin: 0 auto; + background: #eee; + border: 1px solid #eee; + padding: 2px; + outline: 0; + font-size: 12px; +} + +.daterangepicker .calendar-time { + text-align: center; + margin: 4px auto 0 auto; + line-height: 30px; + position: relative; +} + +.daterangepicker .calendar-time select.disabled { + color: #ccc; + cursor: not-allowed; +} + +.daterangepicker .drp-buttons { + clear: both; + text-align: right; + padding: 8px; + border-top: 1px solid #ddd; + display: none; + line-height: 12px; + vertical-align: middle; +} + +.daterangepicker .drp-selected { + display: inline-block; + font-size: 12px; + padding-right: 8px; +} + +.daterangepicker .drp-buttons .btn { + margin-left: 8px; + font-size: 12px; + font-weight: bold; + padding: 4px 8px; +} + +.daterangepicker.show-ranges .drp-calendar.left { + border-left: 1px solid #ddd; +} + +.daterangepicker .ranges { + float: none; + text-align: left; + margin: 0; +} + +.daterangepicker.show-calendar .ranges { + margin-top: 8px; +} + +.daterangepicker .ranges ul { + list-style: none; + margin: 0 auto; + padding: 0; + width: 100%; +} + +.daterangepicker .ranges li { + font-size: 12px; + padding: 8px 12px; + cursor: pointer; +} + +.daterangepicker .ranges li:hover { + background-color: #eee; +} + +.daterangepicker .ranges li.active { + background-color: #08c; + color: #fff; +} + +/* Larger Screen Styling */ +@media (min-width: 564px) { + .daterangepicker { + width: auto; } + .daterangepicker .ranges ul { + width: 140px; } + .daterangepicker.single .ranges ul { + width: 100%; } + .daterangepicker.single .drp-calendar.left { + clear: none; } + .daterangepicker.single.ltr .ranges, .daterangepicker.single.ltr .drp-calendar { + float: left; } + .daterangepicker.single.rtl .ranges, .daterangepicker.single.rtl .drp-calendar { + float: right; } + .daterangepicker.ltr { + direction: ltr; + text-align: left; } + .daterangepicker.ltr .drp-calendar.left { + clear: left; + margin-right: 0; } + .daterangepicker.ltr .drp-calendar.left .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .daterangepicker.ltr .drp-calendar.right { + margin-left: 0; } + .daterangepicker.ltr .drp-calendar.right .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .daterangepicker.ltr .drp-calendar.left .calendar-table { + padding-right: 8px; } + .daterangepicker.ltr .ranges, .daterangepicker.ltr .drp-calendar { + float: left; } + .daterangepicker.rtl { + direction: rtl; + text-align: right; } + .daterangepicker.rtl .drp-calendar.left { + clear: right; + margin-left: 0; } + .daterangepicker.rtl .drp-calendar.left .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; } + .daterangepicker.rtl .drp-calendar.right { + margin-right: 0; } + .daterangepicker.rtl .drp-calendar.right .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; } + .daterangepicker.rtl .drp-calendar.left .calendar-table { + padding-left: 12px; } + .daterangepicker.rtl .ranges, .daterangepicker.rtl .drp-calendar { + text-align: right; + float: right; } } +@media (min-width: 730px) { + .daterangepicker .ranges { + width: auto; } + .daterangepicker.ltr .ranges { + float: left; } + .daterangepicker.rtl .ranges { + float: right; } + .daterangepicker .drp-calendar.left { + clear: none !important; } } diff --git a/public/v1/css/daterangepicker.css b/public/v1/css/daterangepicker-default.css old mode 100755 new mode 100644 similarity index 100% rename from public/v1/css/daterangepicker.css rename to public/v1/css/daterangepicker-default.css diff --git a/public/v1/css/daterangepicker-light.css b/public/v1/css/daterangepicker-light.css new file mode 100644 index 0000000000..e092daa2ba --- /dev/null +++ b/public/v1/css/daterangepicker-light.css @@ -0,0 +1,438 @@ +.daterangepicker { + position: absolute; + color: inherit; + background-color: #fff; + border-radius: 4px; + border: 1px solid #ddd; + width: 278px; + max-width: none; + padding: 0; + margin-top: 7px; + top: 100px; + left: 20px; + z-index: 3001; + display: none; + font-family: sans-serif, Arial; + font-size: 15px; + line-height: 1em; +} + +.daterangepicker .calendar-table { + border: 1px solid #fff; + border-radius: 4px; + background-color: #fff; +} + +.daterangepicker td.available:hover, .daterangepicker th.available:hover { + background-color: #eee; + border-color: transparent; + color: inherit; +} + +.daterangepicker td.off, .daterangepicker td.off.in-range, .daterangepicker td.off.start-date, .daterangepicker td.off.end-date { + background-color: #fff; + border-color: transparent; + color: #999; +} + +.daterangepicker td.in-range { + background-color: #ebf4f8; + border-color: transparent; + color: #000; + border-radius: 0; +} + +.daterangepicker:before, .daterangepicker:after { + position: absolute; + display: inline-block; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker:before { + top: -7px; + border-right: 7px solid transparent; + border-left: 7px solid transparent; + border-bottom: 7px solid #ccc; +} + +.daterangepicker:after { + top: -6px; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; +} + +.daterangepicker.opensleft:before { + right: 9px; +} + +.daterangepicker.opensleft:after { + right: 10px; +} + +.daterangepicker.openscenter:before { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.openscenter:after { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; +} + +.daterangepicker.opensright:before { + left: 9px; +} + +.daterangepicker.opensright:after { + left: 10px; +} + +.daterangepicker.drop-up { + margin-top: -7px; +} + +.daterangepicker.drop-up:before { + top: initial; + bottom: -7px; + border-bottom: initial; + border-top: 7px solid #ccc; +} + +.daterangepicker.drop-up:after { + top: initial; + bottom: -6px; + border-bottom: initial; + border-top: 6px solid #fff; +} + +.daterangepicker.single .daterangepicker .ranges, .daterangepicker.single .drp-calendar { + float: none; +} + +.daterangepicker.single .drp-selected { + display: none; +} + +.daterangepicker.show-calendar .drp-calendar { + display: block; +} + +.daterangepicker.show-calendar .drp-buttons { + display: block; +} + +.daterangepicker.auto-apply .drp-buttons { + display: none; +} + +.daterangepicker .drp-calendar { + display: none; + max-width: 270px; +} + +.daterangepicker .drp-calendar.left { + padding: 8px 0 8px 8px; +} + +.daterangepicker .drp-calendar.right { + padding: 8px; +} + +.daterangepicker .drp-calendar.single .calendar-table { + border: none; +} + +.daterangepicker .calendar-table .next span, .daterangepicker .calendar-table .prev span { + color: #fff; + border: solid black; + border-width: 0 2px 2px 0; + border-radius: 0; + display: inline-block; + padding: 3px; +} + +.daterangepicker .calendar-table .next span { + transform: rotate(-45deg); + -webkit-transform: rotate(-45deg); +} + +.daterangepicker .calendar-table .prev span { + transform: rotate(135deg); + -webkit-transform: rotate(135deg); +} + +.daterangepicker .calendar-table th, .daterangepicker .calendar-table td { + white-space: nowrap; + text-align: center; + vertical-align: middle; + min-width: 32px; + width: 32px; + height: 24px; + line-height: 24px; + font-size: 12px; + border-radius: 4px; + border: 1px solid transparent; + cursor: pointer; +} + +.daterangepicker .calendar-table table { + width: 100%; + margin: 0; + border-spacing: 0; + border-collapse: collapse; +} + + +.daterangepicker td.week, .daterangepicker th.week { + font-size: 80%; + color: #ccc; +} + + +.daterangepicker td.start-date { + border-radius: 4px 0 0 4px; +} + +.daterangepicker td.end-date { + border-radius: 0 4px 4px 0; +} + +.daterangepicker td.start-date.end-date { + border-radius: 4px; +} + +.daterangepicker td.active, .daterangepicker td.active:hover { + background-color: #357ebd; + border-color: transparent; + color: #fff; +} + +.daterangepicker th.month { + width: auto; +} + +.daterangepicker td.disabled, .daterangepicker option.disabled { + color: #999; + cursor: not-allowed; + text-decoration: line-through; +} + +.daterangepicker select.monthselect, .daterangepicker select.yearselect { + font-size: 12px; + padding: 1px; + height: auto; + margin: 0; + cursor: default; +} + +.daterangepicker select.monthselect { + margin-right: 2%; + width: 56%; +} + +.daterangepicker select.yearselect { + width: 40%; +} + +.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect { + width: 50px; + margin: 0 auto; + background: #eee; + border: 1px solid #eee; + padding: 2px; + outline: 0; + font-size: 12px; +} + +.daterangepicker .calendar-time { + text-align: center; + margin: 4px auto 0 auto; + line-height: 30px; + position: relative; +} + +.daterangepicker .calendar-time select.disabled { + color: #ccc; + cursor: not-allowed; +} + +.daterangepicker .drp-buttons { + clear: both; + text-align: right; + padding: 8px; + border-top: 1px solid #ddd; + display: none; + line-height: 12px; + vertical-align: middle; +} + +.daterangepicker .drp-selected { + display: inline-block; + font-size: 12px; + padding-right: 8px; +} + +.daterangepicker .drp-buttons .btn { + margin-left: 8px; + font-size: 12px; + font-weight: bold; + padding: 4px 8px; +} + +.daterangepicker.show-ranges .drp-calendar.left { + border-left: 1px solid #ddd; +} + +.daterangepicker .ranges { + float: none; + text-align: left; + margin: 0; +} + +.daterangepicker.show-calendar .ranges { + margin-top: 8px; +} + +.daterangepicker .ranges ul { + list-style: none; + margin: 0 auto; + padding: 0; + width: 100%; +} + +.daterangepicker .ranges li { + font-size: 12px; + padding: 8px 12px; + cursor: pointer; +} + +.daterangepicker .ranges li:hover { + background-color: #eee; +} + +.daterangepicker .ranges li.active { + background-color: #08c; + color: #fff; +} + +/* Larger Screen Styling */ +@media (min-width: 564px) { + .daterangepicker { + width: auto; + } + + .daterangepicker .ranges ul { + width: 140px; + } + + .daterangepicker.single .ranges ul { + width: 100%; + } + + .daterangepicker.single .drp-calendar.left { + clear: none; + } + + .daterangepicker.single.ltr .ranges, .daterangepicker.single.ltr .drp-calendar { + float: left; + } + + .daterangepicker.single.rtl .ranges, .daterangepicker.single.rtl .drp-calendar { + float: right; + } + + .daterangepicker.ltr { + direction: ltr; + text-align: left; + } + + .daterangepicker.ltr .drp-calendar.left { + clear: left; + margin-right: 0; + } + + .daterangepicker.ltr .drp-calendar.left .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .daterangepicker.ltr .drp-calendar.right { + margin-left: 0; + } + + .daterangepicker.ltr .drp-calendar.right .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .daterangepicker.ltr .drp-calendar.left .calendar-table { + padding-right: 8px; + } + + .daterangepicker.ltr .ranges, .daterangepicker.ltr .drp-calendar { + float: left; + } + + .daterangepicker.rtl { + direction: rtl; + text-align: right; + } + + .daterangepicker.rtl .drp-calendar.left { + clear: right; + margin-left: 0; + } + + .daterangepicker.rtl .drp-calendar.left .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + + .daterangepicker.rtl .drp-calendar.right { + margin-right: 0; + } + + .daterangepicker.rtl .drp-calendar.right .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + + .daterangepicker.rtl .drp-calendar.left .calendar-table { + padding-left: 12px; + } + + .daterangepicker.rtl .ranges, .daterangepicker.rtl .drp-calendar { + text-align: right; + float: right; + } +} + +@media (min-width: 730px) { + .daterangepicker .ranges { + width: auto; + } + + .daterangepicker.ltr .ranges { + float: left; + } + + .daterangepicker.rtl .ranges { + float: right; + } + + .daterangepicker .drp-calendar.left { + clear: none !important; + } +} diff --git a/public/v1/lib/adminlte/css/skins/skin-dark.css b/public/v1/lib/adminlte/css/skins/skin-dark.css index b10bc50ae3..51ec6e2d44 100644 --- a/public/v1/lib/adminlte/css/skins/skin-dark.css +++ b/public/v1/lib/adminlte/css/skins/skin-dark.css @@ -138,9 +138,6 @@ .skin-firefly-iii .form-control { color: #bec5cb; } -.skin-firefly-iii .vue-tags-input { - background: #353c42 !important; -} .skin-firefly-iii .ti-input { border: 1px solid #353c42 !important; } diff --git a/public/v1/lib/adminlte/css/skins/skin-dark.min.css b/public/v1/lib/adminlte/css/skins/skin-dark.min.css index 57161e1842..bb178f8f08 100644 --- a/public/v1/lib/adminlte/css/skins/skin-dark.min.css +++ b/public/v1/lib/adminlte/css/skins/skin-dark.min.css @@ -1 +1 @@ -.skin-firefly-iii{color:#bec5cb}.skin-firefly-iii .well{background-color:#55606a;border-color:#454e56}.skin-firefly-iii .alert-success>a{color:#fff}.skin-firefly-iii .text-muted{color:#b0b8c0}.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#00ad5d}.skin-firefly-iii .money-negative{color:#e47365}.skin-firefly-iii .money-transfer{color:#47b2f5}.skin-firefly-iii h1 small,.skin-firefly-iii h3 small{color:#bec5cb}.skin-firefly-iii .breadcrumb .active{color:#bec5cb}.skin-firefly-iii .progress{background-color:#3a4148}.skin-firefly-iii .bootstrap-tagsinput{background-color:#353c42;border:1px solid #353c42 !important}.skin-firefly-iii .bg-aqua-gradient{background:#004f63 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #004f63), color-stop(1, #006b87)) !important;background:-ms-linear-gradient(bottom, #004f63, #006b87) !important;background:-moz-linear-gradient(center bottom, #004f63 0%, #006b87 100%) !important;background:-o-linear-gradient(#006b87, #004f63) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#006b87', endColorstr='#004f63', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-teal-gradient{background:#1b6262 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #1b6262), color-stop(1, #2da2a2)) !important;background:-ms-linear-gradient(bottom, #1b6262, #2da2a2) !important;background:-moz-linear-gradient(center bottom, #1b6262 0%, #2da2a2 100%) !important;background:-o-linear-gradient(#2da2a2, #1b6262) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2da2a2', endColorstr='#1b6262', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-green-gradient{background:#006034 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #006034), color-stop(1, #008447)) !important;background:-ms-linear-gradient(bottom, #006034, #008447) !important;background:-moz-linear-gradient(center bottom, #006034 0%, #008447 100%) !important;background:-o-linear-gradient(#008447, #006034) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#008447', endColorstr='#006034', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-blue-gradient{background:#075383 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #075383), color-stop(1, #0968a5)) !important;background:-ms-linear-gradient(bottom, #075383, #0968a5) !important;background:-moz-linear-gradient(center bottom, #075383 0%, #0968a5 100%) !important;background:-o-linear-gradient(#0968a5, #075383) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0968a5', endColorstr='#075383', GradientType=0) !important;color:#fff}.skin-firefly-iii a{color:#5fa4cc}.skin-firefly-iii a.list-group-item,.skin-firefly-iii button.list-group-item{color:#bec5cb}.skin-firefly-iii .btn-default{background-color:#55606a;color:#bec5cb;border-color:#454e56}.skin-firefly-iii .btn-default:hover,.skin-firefly-iii .btn-default:active,.skin-firefly-iii .btn-default.hover{background-color:#4a535c}.skin-firefly-iii .btn-success{color:#bec5cb;background-color:#006034;border-color:#004726}.skin-firefly-iii .btn-success:hover,.skin-firefly-iii .btn-success:active,.skin-firefly-iii .btn-success.hover{background-color:#004726}.skin-firefly-iii .dropdown-menu{box-shadow:none;background-color:#353c42;border-color:#454e56}.skin-firefly-iii .dropdown-menu>li>a{color:#bec5cb}.skin-firefly-iii .dropdown-menu>li>a>.glyphicon,.skin-firefly-iii .dropdown-menu>li>a>.fa,.skin-firefly-iii .dropdown-menu>li>a>.ion{margin-right:10px}.skin-firefly-iii .dropdown-menu>li>a:hover{background-color:#404950}.skin-firefly-iii .dropdown-menu>.divider{background-color:#eee}.skin-firefly-iii .dropdown-menu>li>a{color:#bec5cb !important}.skin-firefly-iii .table-striped>tbody>tr:nth-of-type(odd){background-color:#373f45}.skin-firefly-iii .table-hover>tbody>tr:hover{background-color:#454e56}.skin-firefly-iii .form-control{color:#bec5cb}.skin-firefly-iii .vue-tags-input{background:#353c42 !important}.skin-firefly-iii .ti-input{border:1px solid #353c42 !important}.skin-firefly-iii code{background-color:#343941;color:#c9d1d9}.skin-firefly-iii .modal-content{position:relative;background-color:#353c42;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.skin-firefly-iii .pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.skin-firefly-iii .pagination>li{display:inline}.skin-firefly-iii .pagination>li>a,.skin-firefly-iii .pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#3c8dbc;background-color:#454e56;border:1px solid #15181a;margin-left:-1px}.skin-firefly-iii .pagination>li:first-child>a,.skin-firefly-iii .pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.skin-firefly-iii .pagination>li:last-child>a,.skin-firefly-iii .pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.skin-firefly-iii .pagination>li>a:hover,.skin-firefly-iii .pagination>li>span:hover,.skin-firefly-iii .pagination>li>a:focus,.skin-firefly-iii .pagination>li>span:focus{z-index:2;color:#72afd2;background-color:#4c565e;border-color:#ddd}.skin-firefly-iii .pagination>.active>a,.skin-firefly-iii .pagination>.active>span,.skin-firefly-iii .pagination>.active>a:hover,.skin-firefly-iii .pagination>.active>span:hover,.skin-firefly-iii .pagination>.active>a:focus,.skin-firefly-iii .pagination>.active>span:focus{z-index:3;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.skin-firefly-iii .pagination>.disabled>span,.skin-firefly-iii .pagination>.disabled>span:hover,.skin-firefly-iii .pagination>.disabled>span:focus,.skin-firefly-iii .pagination>.disabled>a,.skin-firefly-iii .pagination>.disabled>a:hover,.skin-firefly-iii .pagination>.disabled>a:focus{color:#777;background-color:#57636c;border-color:#15181a;cursor:not-allowed}.skin-firefly-iii .text-warning{color:#f39c12 !important}.skin-firefly-iii a.text-warning:hover,.skin-firefly-iii a.text-warning:focus{color:#d39e00 !important}.skin-firefly-iii h4{color:#44DEF1}.skin-firefly-iii .content-header>.breadcrumb>li>a{color:#bec5cb}.skin-firefly-iii .table>thead>tr>th,.skin-firefly-iii .table>tbody>tr>th,.skin-firefly-iii .table>tfoot>tr>th,.skin-firefly-iii .table>thead>tr>td,.skin-firefly-iii .table>tbody>tr>td,.skin-firefly-iii .table>tfoot>tr>td{color:#bec5cb;border-top:0px}.skin-firefly-iii .table>thead>tr.odd,.skin-firefly-iii .table>tbody>tr.odd,.skin-firefly-iii .table>tfoot>tr.odd{background-color:#2a2f34}.skin-firefly-iii .table>thead>tr.odd:hover,.skin-firefly-iii .table>tbody>tr.odd:hover,.skin-firefly-iii .table>tfoot>tr.odd:hover,.skin-firefly-iii .table>thead>tr.even:hover,.skin-firefly-iii .table>tbody>tr.even:hover,.skin-firefly-iii .table>tfoot>tr.even:hover{background-color:#1e2226}.skin-firefly-iii .table-bordered>thead>tr>th,.skin-firefly-iii .table-bordered>tbody>tr>th,.skin-firefly-iii .table-bordered>tfoot>tr>th,.skin-firefly-iii .table-bordered>thead>tr>td,.skin-firefly-iii .table-bordered>tbody>tr>td,.skin-firefly-iii .table-bordered>tfoot>tr>td{border:1px solid #353c42}.skin-firefly-iii .dataTables_wrapper input[type='search']{border-radius:4px;background-color:#353c42;border:0;color:#bec5cb}.skin-firefly-iii .dataTables_paginate .pagination li>a{background-color:transparent !important;border:0}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#272c30}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#fff}.skin-firefly-iii .sidebar-menu>li.header{color:#556068;background:#1e2225}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a,.skin-firefly-iii .sidebar-menu>li.menu-open>a{color:#fff;background:#22272a}.skin-firefly-iii .sidebar-menu>li.active>a{border-left-color:#272c30}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#32393e}.skin-firefly-iii .sidebar a{color:#bec5cb}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#949fa8}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#fff}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #3e464c;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#3e464c;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-firefly-iii .box,.skin-firefly-iii .box-footer,.skin-firefly-iii .info-box,.skin-firefly-iii .box-comment,.skin-firefly-iii .comment-text,.skin-firefly-iii .comment-text .username{color:#bec5cb;background-color:#272c30}.skin-firefly-iii .box-comments .box-comment{border-bottom-color:#353c42}.skin-firefly-iii .box-footer{border-top:1px solid #353c42}.skin-firefly-iii .box-header.with-border{border-bottom:1px solid #353c42}.skin-firefly-iii .box-solid,.skin-firefly-iii .box{border:1px solid #272c30}.skin-firefly-iii .box-solid>.box-header,.skin-firefly-iii .box>.box-header{color:#bec5cb;background:#272c30;background-color:#272c30}.skin-firefly-iii .box-solid>.box-header a,.skin-firefly-iii .box>.box-header a,.skin-firefly-iii .box-solid>.box-header .btn,.skin-firefly-iii .box>.box-header .btn{color:#bec5cb}.skin-firefly-iii .box.box-info,.skin-firefly-iii .box.box-primary,.skin-firefly-iii .box.box-success,.skin-firefly-iii .box.box-warning,.skin-firefly-iii .box.box-danger{border-top-width:3px}.skin-firefly-iii .box.box-info{border-top-color:#004f63}.skin-firefly-iii .box.box-primary{border-top-color:#075383}.skin-firefly-iii .box.box-success{border-top-color:#006034}.skin-firefly-iii .box.box-warning{border-top-color:#FF851B}.skin-firefly-iii .box.box-danger{border-top-color:#dd4b39}.skin-firefly-iii .main-header .navbar{background-color:#272c30}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#bec5cb}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#bec5cb}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .timeline li .timeline-item{color:#bec5cb;background-color:#272c30;border-color:#353c42}.skin-firefly-iii .timeline li .timeline-header{border-bottom-color:#353c42}.skin-firefly-iii .nav-stacked>li>a{color:#bec5cb}.skin-firefly-iii .nav-stacked>li>a:hover{color:white;background-color:#1e2226}.skin-firefly-iii .content-wrapper,.skin-firefly-iii .right-side{background-color:#353c42}.skin-firefly-iii .main-footer,.skin-firefly-iii .nav-tabs-custom{background-color:#272c30;border-top-color:#353c42;color:#bec5cb}.skin-firefly-iii .main-footer .nav-tabs,.skin-firefly-iii .nav-tabs-custom .nav-tabs{border-bottom-color:#353c42}.skin-firefly-iii .main-footer .tab-content,.skin-firefly-iii .nav-tabs-custom .tab-content{background-color:#272c30}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li.active>a{border-left-color:#353c42;border-right-color:#353c42}.skin-firefly-iii .nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#353c42}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li{color:#bec5cb}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li.active>a{background-color:#272c30}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li>a{color:#bec5cb}.skin-firefly-iii .form-group .input-group-addon,.skin-firefly-iii .input-group .input-group-addon,.skin-firefly-iii .form-group input,.skin-firefly-iii .input-group input,.skin-firefly-iii .form-group textarea,.skin-firefly-iii .input-group textarea{background-color:#353c42;color:#bec5cb;border:1px solid #73818f}.skin-firefly-iii .list-group{color:#bec5cb;background-color:#272c30}.skin-firefly-iii .list-group .list-group-item{border-color:#353c42;background-color:#272c30}.skin-firefly-iii .input-group .input-group-addon{border-right:1px solid #272c30}.skin-firefly-iii .form-control{border-color:#272c30;background-color:#353c42}.skin-firefly-iii .select2 .select2-selection{background-color:#353c42;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2 .select2-selection .select2-container--default,.skin-firefly-iii .select2 .select2-selection .select2-selection--single,.skin-firefly-iii .select2 .select2-selection .select2-selection--multiple,.skin-firefly-iii .select2 .select2-selection .select2-selection__rendered{color:#bec5cb}.skin-firefly-iii .select2-dropdown{background-color:#353c42;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2-dropdown .select2-search__field{background-color:#272c30;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2-container--default.select2-container--open{background-color:#272c30}.skin-firefly-iii .sidebar-menu>li.header{color:#85929e} \ No newline at end of file +.skin-firefly-iii{color:#bec5cb}.skin-firefly-iii .well{background-color:#55606a;border-color:#454e56}.skin-firefly-iii .alert-success>a{color:#fff}.skin-firefly-iii .text-muted{color:#b0b8c0}.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#00ad5d}.skin-firefly-iii .money-negative{color:#e47365}.skin-firefly-iii .money-transfer{color:#47b2f5}.skin-firefly-iii h1 small,.skin-firefly-iii h3 small{color:#bec5cb}.skin-firefly-iii .breadcrumb .active{color:#bec5cb}.skin-firefly-iii .progress{background-color:#3a4148}.skin-firefly-iii .bootstrap-tagsinput{background-color:#353c42;border:1px solid #353c42 !important}.skin-firefly-iii .bg-aqua-gradient{background:#004f63 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #004f63), color-stop(1, #006b87)) !important;background:-ms-linear-gradient(bottom, #004f63, #006b87) !important;background:-moz-linear-gradient(center bottom, #004f63 0%, #006b87 100%) !important;background:-o-linear-gradient(#006b87, #004f63) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#006b87', endColorstr='#004f63', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-teal-gradient{background:#1b6262 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #1b6262), color-stop(1, #2da2a2)) !important;background:-ms-linear-gradient(bottom, #1b6262, #2da2a2) !important;background:-moz-linear-gradient(center bottom, #1b6262 0%, #2da2a2 100%) !important;background:-o-linear-gradient(#2da2a2, #1b6262) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#2da2a2', endColorstr='#1b6262', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-green-gradient{background:#006034 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #006034), color-stop(1, #008447)) !important;background:-ms-linear-gradient(bottom, #006034, #008447) !important;background:-moz-linear-gradient(center bottom, #006034 0%, #008447 100%) !important;background:-o-linear-gradient(#008447, #006034) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#008447', endColorstr='#006034', GradientType=0) !important;color:#fff}.skin-firefly-iii .bg-blue-gradient{background:#075383 !important;background:-webkit-gradient(linear, left bottom, left top, color-stop(0, #075383), color-stop(1, #0968a5)) !important;background:-ms-linear-gradient(bottom, #075383, #0968a5) !important;background:-moz-linear-gradient(center bottom, #075383 0%, #0968a5 100%) !important;background:-o-linear-gradient(#0968a5, #075383) !important;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#0968a5', endColorstr='#075383', GradientType=0) !important;color:#fff}.skin-firefly-iii a{color:#5fa4cc}.skin-firefly-iii a.list-group-item,.skin-firefly-iii button.list-group-item{color:#bec5cb}.skin-firefly-iii .btn-default{background-color:#55606a;color:#bec5cb;border-color:#454e56}.skin-firefly-iii .btn-default:hover,.skin-firefly-iii .btn-default:active,.skin-firefly-iii .btn-default.hover{background-color:#4a535c}.skin-firefly-iii .btn-success{color:#bec5cb;background-color:#006034;border-color:#004726}.skin-firefly-iii .btn-success:hover,.skin-firefly-iii .btn-success:active,.skin-firefly-iii .btn-success.hover{background-color:#004726}.skin-firefly-iii .dropdown-menu{box-shadow:none;background-color:#353c42;border-color:#454e56}.skin-firefly-iii .dropdown-menu>li>a{color:#bec5cb}.skin-firefly-iii .dropdown-menu>li>a>.glyphicon,.skin-firefly-iii .dropdown-menu>li>a>.fa,.skin-firefly-iii .dropdown-menu>li>a>.ion{margin-right:10px}.skin-firefly-iii .dropdown-menu>li>a:hover{background-color:#404950}.skin-firefly-iii .dropdown-menu>.divider{background-color:#eee}.skin-firefly-iii .dropdown-menu>li>a{color:#bec5cb !important}.skin-firefly-iii .table-striped>tbody>tr:nth-of-type(odd){background-color:#373f45}.skin-firefly-iii .table-hover>tbody>tr:hover{background-color:#454e56}.skin-firefly-iii .form-control{color:#bec5cb}.skin-firefly-iii .ti-input{border:1px solid #353c42 !important}.skin-firefly-iii code{background-color:#343941;color:#c9d1d9}.skin-firefly-iii .modal-content{position:relative;background-color:#353c42;border:1px solid #999;border:1px solid rgba(0,0,0,0.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,0.5);box-shadow:0 3px 9px rgba(0,0,0,0.5);background-clip:padding-box;outline:0}.skin-firefly-iii .pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.skin-firefly-iii .pagination>li{display:inline}.skin-firefly-iii .pagination>li>a,.skin-firefly-iii .pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#3c8dbc;background-color:#454e56;border:1px solid #15181a;margin-left:-1px}.skin-firefly-iii .pagination>li:first-child>a,.skin-firefly-iii .pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.skin-firefly-iii .pagination>li:last-child>a,.skin-firefly-iii .pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.skin-firefly-iii .pagination>li>a:hover,.skin-firefly-iii .pagination>li>span:hover,.skin-firefly-iii .pagination>li>a:focus,.skin-firefly-iii .pagination>li>span:focus{z-index:2;color:#72afd2;background-color:#4c565e;border-color:#ddd}.skin-firefly-iii .pagination>.active>a,.skin-firefly-iii .pagination>.active>span,.skin-firefly-iii .pagination>.active>a:hover,.skin-firefly-iii .pagination>.active>span:hover,.skin-firefly-iii .pagination>.active>a:focus,.skin-firefly-iii .pagination>.active>span:focus{z-index:3;color:#fff;background-color:#337ab7;border-color:#337ab7;cursor:default}.skin-firefly-iii .pagination>.disabled>span,.skin-firefly-iii .pagination>.disabled>span:hover,.skin-firefly-iii .pagination>.disabled>span:focus,.skin-firefly-iii .pagination>.disabled>a,.skin-firefly-iii .pagination>.disabled>a:hover,.skin-firefly-iii .pagination>.disabled>a:focus{color:#777;background-color:#57636c;border-color:#15181a;cursor:not-allowed}.skin-firefly-iii .text-warning{color:#f39c12 !important}.skin-firefly-iii a.text-warning:hover,.skin-firefly-iii a.text-warning:focus{color:#d39e00 !important}.skin-firefly-iii h4{color:#44DEF1}.skin-firefly-iii .content-header>.breadcrumb>li>a{color:#bec5cb}.skin-firefly-iii .table>thead>tr>th,.skin-firefly-iii .table>tbody>tr>th,.skin-firefly-iii .table>tfoot>tr>th,.skin-firefly-iii .table>thead>tr>td,.skin-firefly-iii .table>tbody>tr>td,.skin-firefly-iii .table>tfoot>tr>td{color:#bec5cb;border-top:0px}.skin-firefly-iii .table>thead>tr.odd,.skin-firefly-iii .table>tbody>tr.odd,.skin-firefly-iii .table>tfoot>tr.odd{background-color:#2a2f34}.skin-firefly-iii .table>thead>tr.odd:hover,.skin-firefly-iii .table>tbody>tr.odd:hover,.skin-firefly-iii .table>tfoot>tr.odd:hover,.skin-firefly-iii .table>thead>tr.even:hover,.skin-firefly-iii .table>tbody>tr.even:hover,.skin-firefly-iii .table>tfoot>tr.even:hover{background-color:#1e2226}.skin-firefly-iii .table-bordered>thead>tr>th,.skin-firefly-iii .table-bordered>tbody>tr>th,.skin-firefly-iii .table-bordered>tfoot>tr>th,.skin-firefly-iii .table-bordered>thead>tr>td,.skin-firefly-iii .table-bordered>tbody>tr>td,.skin-firefly-iii .table-bordered>tfoot>tr>td{border:1px solid #353c42}.skin-firefly-iii .dataTables_wrapper input[type='search']{border-radius:4px;background-color:#353c42;border:0;color:#bec5cb}.skin-firefly-iii .dataTables_paginate .pagination li>a{background-color:transparent !important;border:0}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#272c30}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#fff}.skin-firefly-iii .sidebar-menu>li.header{color:#556068;background:#1e2225}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a,.skin-firefly-iii .sidebar-menu>li.menu-open>a{color:#fff;background:#22272a}.skin-firefly-iii .sidebar-menu>li.active>a{border-left-color:#272c30}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{margin:0 1px;background:#32393e}.skin-firefly-iii .sidebar a{color:#bec5cb}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#949fa8}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#fff}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #3e464c;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#3e464c;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}.skin-firefly-iii .box,.skin-firefly-iii .box-footer,.skin-firefly-iii .info-box,.skin-firefly-iii .box-comment,.skin-firefly-iii .comment-text,.skin-firefly-iii .comment-text .username{color:#bec5cb;background-color:#272c30}.skin-firefly-iii .box-comments .box-comment{border-bottom-color:#353c42}.skin-firefly-iii .box-footer{border-top:1px solid #353c42}.skin-firefly-iii .box-header.with-border{border-bottom:1px solid #353c42}.skin-firefly-iii .box-solid,.skin-firefly-iii .box{border:1px solid #272c30}.skin-firefly-iii .box-solid>.box-header,.skin-firefly-iii .box>.box-header{color:#bec5cb;background:#272c30;background-color:#272c30}.skin-firefly-iii .box-solid>.box-header a,.skin-firefly-iii .box>.box-header a,.skin-firefly-iii .box-solid>.box-header .btn,.skin-firefly-iii .box>.box-header .btn{color:#bec5cb}.skin-firefly-iii .box.box-info,.skin-firefly-iii .box.box-primary,.skin-firefly-iii .box.box-success,.skin-firefly-iii .box.box-warning,.skin-firefly-iii .box.box-danger{border-top-width:3px}.skin-firefly-iii .box.box-info{border-top-color:#004f63}.skin-firefly-iii .box.box-primary{border-top-color:#075383}.skin-firefly-iii .box.box-success{border-top-color:#006034}.skin-firefly-iii .box.box-warning{border-top-color:#FF851B}.skin-firefly-iii .box.box-danger{border-top-color:#dd4b39}.skin-firefly-iii .main-header .navbar{background-color:#272c30}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#bec5cb}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#bec5cb}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .timeline li .timeline-item{color:#bec5cb;background-color:#272c30;border-color:#353c42}.skin-firefly-iii .timeline li .timeline-header{border-bottom-color:#353c42}.skin-firefly-iii .nav-stacked>li>a{color:#bec5cb}.skin-firefly-iii .nav-stacked>li>a:hover{color:white;background-color:#1e2226}.skin-firefly-iii .content-wrapper,.skin-firefly-iii .right-side{background-color:#353c42}.skin-firefly-iii .main-footer,.skin-firefly-iii .nav-tabs-custom{background-color:#272c30;border-top-color:#353c42;color:#bec5cb}.skin-firefly-iii .main-footer .nav-tabs,.skin-firefly-iii .nav-tabs-custom .nav-tabs{border-bottom-color:#353c42}.skin-firefly-iii .main-footer .tab-content,.skin-firefly-iii .nav-tabs-custom .tab-content{background-color:#272c30}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li.active>a{border-left-color:#353c42;border-right-color:#353c42}.skin-firefly-iii .nav-tabs-custom>.nav-tabs.pull-right>li:first-of-type.active>a{border-left-color:#353c42}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li{color:#bec5cb}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li.active>a{background-color:#272c30}.skin-firefly-iii .nav-tabs-custom>.nav-tabs>li>a{color:#bec5cb}.skin-firefly-iii .form-group .input-group-addon,.skin-firefly-iii .input-group .input-group-addon,.skin-firefly-iii .form-group input,.skin-firefly-iii .input-group input,.skin-firefly-iii .form-group textarea,.skin-firefly-iii .input-group textarea{background-color:#353c42;color:#bec5cb;border:1px solid #73818f}.skin-firefly-iii .list-group{color:#bec5cb;background-color:#272c30}.skin-firefly-iii .list-group .list-group-item{border-color:#353c42;background-color:#272c30}.skin-firefly-iii .input-group .input-group-addon{border-right:1px solid #272c30}.skin-firefly-iii .form-control{border-color:#272c30;background-color:#353c42}.skin-firefly-iii .select2 .select2-selection{background-color:#353c42;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2 .select2-selection .select2-container--default,.skin-firefly-iii .select2 .select2-selection .select2-selection--single,.skin-firefly-iii .select2 .select2-selection .select2-selection--multiple,.skin-firefly-iii .select2 .select2-selection .select2-selection__rendered{color:#bec5cb}.skin-firefly-iii .select2-dropdown{background-color:#353c42;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2-dropdown .select2-search__field{background-color:#272c30;color:#bec5cb;border:1px solid #353c42}.skin-firefly-iii .select2-container--default.select2-container--open{background-color:#272c30}.skin-firefly-iii .sidebar-menu>li.header{color:#85929e} \ No newline at end of file diff --git a/public/v1/lib/adminlte/css/skins/skin-light.css b/public/v1/lib/adminlte/css/skins/skin-light.css index f11740dff5..37f2d9db03 100644 --- a/public/v1/lib/adminlte/css/skins/skin-light.css +++ b/public/v1/lib/adminlte/css/skins/skin-light.css @@ -15,6 +15,12 @@ .skin-firefly-iii .money-transfer { color: #31708f; } +.skin-firefly-iii .ti-new-tag-input { + background: #fff; +} +.skin-firefly-iii input { + background: #ecf0f5 !important; +} .skin-firefly-iii .main-header .navbar { background-color: #3c8dbc; } diff --git a/public/v1/lib/adminlte/css/skins/skin-light.min.css b/public/v1/lib/adminlte/css/skins/skin-light.min.css index 46bf3beb57..9935e6992e 100644 --- a/public/v1/lib/adminlte/css/skins/skin-light.min.css +++ b/public/v1/lib/adminlte/css/skins/skin-light.min.css @@ -1 +1 @@ -.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#3c763d}.skin-firefly-iii .money-negative{color:#a94442}.skin-firefly-iii .money-transfer{color:#31708f}.skin-firefly-iii .main-header .navbar{background-color:#3c8dbc}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#fff}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-firefly-iii .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-firefly-iii .main-header .navbar .dropdown-menu li a{color:#fff}.skin-firefly-iii .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-firefly-iii .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-firefly-iii .main-header .logo:hover{background-color:#3b8ab8}.skin-firefly-iii .main-header li.user-header{background-color:#3c8dbc}.skin-firefly-iii .content-header{background:transparent}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#f9fafc}.skin-firefly-iii .main-sidebar{border-right:1px solid #d2d6de}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#444}.skin-firefly-iii .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-firefly-iii .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-firefly-iii .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-firefly-iii .sidebar-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-firefly-iii .sidebar a{color:#444}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#777}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#000}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-firefly-iii.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-firefly-iii .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file +.skin-firefly-iii .money-neutral{color:#999}.skin-firefly-iii .money-positive{color:#3c763d}.skin-firefly-iii .money-negative{color:#a94442}.skin-firefly-iii .money-transfer{color:#31708f}.skin-firefly-iii .ti-new-tag-input{background:#fff}.skin-firefly-iii input{background:#ecf0f5 !important}.skin-firefly-iii .main-header .navbar{background-color:#3c8dbc}.skin-firefly-iii .main-header .navbar .nav>li>a{color:#fff}.skin-firefly-iii .main-header .navbar .nav>li>a:hover,.skin-firefly-iii .main-header .navbar .nav>li>a:active,.skin-firefly-iii .main-header .navbar .nav>li>a:focus,.skin-firefly-iii .main-header .navbar .nav .open>a,.skin-firefly-iii .main-header .navbar .nav .open>a:hover,.skin-firefly-iii .main-header .navbar .nav .open>a:focus,.skin-firefly-iii .main-header .navbar .nav>.active>a{background:rgba(0,0,0,0.1);color:#f6f6f6}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{color:#f6f6f6;background:rgba(0,0,0,0.1)}.skin-firefly-iii .main-header .navbar .sidebar-toggle{color:#fff}.skin-firefly-iii .main-header .navbar .sidebar-toggle:hover{background-color:#367fa9}@media (max-width:767px){.skin-firefly-iii .main-header .navbar .dropdown-menu li.divider{background-color:rgba(255,255,255,0.1)}.skin-firefly-iii .main-header .navbar .dropdown-menu li a{color:#fff}.skin-firefly-iii .main-header .navbar .dropdown-menu li a:hover{background:#367fa9}}.skin-firefly-iii .main-header .logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-firefly-iii .main-header .logo:hover{background-color:#3b8ab8}.skin-firefly-iii .main-header li.user-header{background-color:#3c8dbc}.skin-firefly-iii .content-header{background:transparent}.skin-firefly-iii .wrapper,.skin-firefly-iii .main-sidebar,.skin-firefly-iii .left-side{background-color:#f9fafc}.skin-firefly-iii .main-sidebar{border-right:1px solid #d2d6de}.skin-firefly-iii .user-panel>.info,.skin-firefly-iii .user-panel>.info>a{color:#444}.skin-firefly-iii .sidebar-menu>li{-webkit-transition:border-left-color .3s ease;-o-transition:border-left-color .3s ease;transition:border-left-color .3s ease}.skin-firefly-iii .sidebar-menu>li.header{color:#848484;background:#f9fafc}.skin-firefly-iii .sidebar-menu>li>a{border-left:3px solid transparent;font-weight:600}.skin-firefly-iii .sidebar-menu>li:hover>a,.skin-firefly-iii .sidebar-menu>li.active>a{color:#000;background:#f4f4f5}.skin-firefly-iii .sidebar-menu>li.active{border-left-color:#3c8dbc}.skin-firefly-iii .sidebar-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-menu>li>.treeview-menu{background:#f4f4f5}.skin-firefly-iii .sidebar a{color:#444}.skin-firefly-iii .sidebar a:hover{text-decoration:none}.skin-firefly-iii .sidebar-menu .treeview-menu>li>a{color:#777}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a,.skin-firefly-iii .sidebar-menu .treeview-menu>li>a:hover{color:#000}.skin-firefly-iii .sidebar-menu .treeview-menu>li.active>a{font-weight:600}.skin-firefly-iii .sidebar-form{border-radius:3px;border:1px solid #d2d6de;margin:10px 10px}.skin-firefly-iii .sidebar-form input[type="text"],.skin-firefly-iii .sidebar-form .btn{box-shadow:none;background-color:#fff;border:1px solid transparent;height:35px}.skin-firefly-iii .sidebar-form input[type="text"]{color:#666;border-top-left-radius:2px;border-top-right-radius:0;border-bottom-right-radius:0;border-bottom-left-radius:2px}.skin-firefly-iii .sidebar-form input[type="text"]:focus,.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{background-color:#fff;color:#666}.skin-firefly-iii .sidebar-form input[type="text"]:focus+.input-group-btn .btn{border-left-color:#fff}.skin-firefly-iii .sidebar-form .btn{color:#999;border-top-left-radius:0;border-top-right-radius:2px;border-bottom-right-radius:2px;border-bottom-left-radius:0}@media (min-width:768px){.skin-firefly-iii.sidebar-mini.sidebar-collapse .sidebar-menu>li>.treeview-menu{border-left:1px solid #d2d6de}}.skin-firefly-iii .main-footer{border-top-color:#d2d6de}.skin-blue.layout-top-nav .main-header>.logo{background-color:#3c8dbc;color:#fff;border-bottom:0 solid transparent}.skin-blue.layout-top-nav .main-header>.logo:hover{background-color:#3b8ab8} \ No newline at end of file diff --git a/readme.md b/readme.md index d6ca2c9f9e..5c5d1378c4 100644 --- a/readme.md +++ b/readme.md @@ -90,20 +90,20 @@ Firefly III is pretty feature packed. Some important stuff first: The most exciting features are: -* Create [recurring transactions to manage your money](https://docs.firefly-iii.org/advanced-concepts/recurring). -* [Rule based transaction handling](https://docs.firefly-iii.org/advanced-concepts/rules) with the ability to create your own rules. +* Create [recurring transactions to manage your money](https://docs.firefly-iii.org/firefly-iii/financial-concepts/recurring/). +* [Rule based transaction handling](https://docs.firefly-iii.org/firefly-iii/features/rules/) with the ability to create your own rules. Then the things that make you go "yeah OK, makes sense". * A [double-entry](https://en.wikipedia.org/wiki/Double-entry_bookkeeping_system) bookkeeping system. -* Save towards a goal using [piggy banks](https://docs.firefly-iii.org/advanced-concepts/piggies). -* View [income and expense reports](https://docs.firefly-iii.org/advanced-concepts/reports). +* Save towards a goal using [piggy banks](https://docs.firefly-iii.org/firefly-iii/financial-concepts/piggies/). +* View [income and expense reports](https://docs.firefly-iii.org/firefly-iii/features/reports/). And the things you would hope for but not expect: * 2 factor authentication for extra security 🔒. -* Supports [any currency you want](https://docs.firefly-iii.org/concepts/currencies), including crypto currencies such as ₿itcoin and Ξthereum. -* There is a [Docker image](https://docs.firefly-iii.org/installation/docker) and an [Heroku script](https://docs.firefly-iii.org/installation/third_parties). +* Supports [any currency you want](https://docs.firefly-iii.org/firefly-iii/financial-concepts/currencies/), including crypto currencies such as ₿itcoin and Ξthereum. +* There is a [Docker image](https://docs.firefly-iii.org/firefly-iii/installation/docker/) and an [Heroku script](https://docs.firefly-iii.org/firefly-iii/installation/third-parties/). And to organise everything: @@ -111,7 +111,7 @@ And to organise everything: * Easy navigation through your records. * Lots of charts because we all love them. -Many more features are listed in the [documentation](https://docs.firefly-iii.org/about-firefly-iii/introduction). +Many more features are listed in the [documentation](https://docs.firefly-iii.org/firefly-iii/about-firefly-iii/introduction/). ## Who's it for? Firefly III on iPhone @@ -122,7 +122,7 @@ Many more features are listed in the [documentation](https://docs.firefly-iii.or ## The Firefly III eco-system -Several users have built pretty awesome stuff around the Firefly III API. [Check out these tools in the documentation](https://docs.firefly-iii.org/other-pages/3rdparty). +Several users have built pretty awesome stuff around the Firefly III API. [Check out these tools in the documentation](https://docs.firefly-iii.org/firefly-iii/more-information/3rdparty/). ## Getting Started @@ -141,9 +141,9 @@ There are many ways to run Firefly III You can contact me at [james@firefly-iii.org](mailto:james@firefly-iii.org), you may open an issue in the [main repository](https://github.com/firefly-iii/firefly-iii) or contact me through [gitter](https://gitter.im/firefly-iii/firefly-iii) and [Mastodon](https://fosstodon.org/@ff3). -Of course, there are some [contributing guidelines](https://docs.firefly-iii.org/firefly-iii/other-pages/contributing) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/main/.github/code_of_conduct.md), which I invite you to check out. +Of course, there are some [contributing guidelines](https://docs.firefly-iii.org/firefly-iii/support/#contributing-code) and a [code of conduct](https://github.com/firefly-iii/firefly-iii/blob/main/.github/code_of_conduct.md), which I invite you to check out. -I can always use your help [squashing bugs](https://docs.firefly-iii.org/support/contribute#bugs), thinking about [new features](https://docs.firefly-iii.org/support/contribute#feature-requests) or [translating Firefly III](https://docs.firefly-iii.org/support/contribute#translations) into other languages. +I can always use your help [squashing bugs](https://docs.firefly-iii.org/data-importer/support/issues/), thinking about [new features](https://docs.firefly-iii.org/data-importer/support/issues/) or [translating Firefly III](https://docs.firefly-iii.org/firefly-iii/faq/translate/) into other languages. [Sonarcloud][sc-project-url] scans the code of Firefly III. If you want to help improve Firefly III, check out the latest reports and take your pick! diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index d90ab1969b..d2b91ef4a4 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -231,8 +231,8 @@ return [ 'generic_invalid_source' => 'You can\'t use this account as the source account.', 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', - 'generic_no_source' => 'You must submit source account information.', - 'generic_no_destination' => 'You must submit destination account information.', + 'generic_no_source' => 'You must submit source account information or submit a transaction journal ID.', + 'generic_no_destination' => 'You must submit destination account information or submit a transaction journal ID.', 'gte.numeric' => 'The :attribute must be greater than or equal to :value.', 'gt.numeric' => 'The :attribute must be greater than :value.', diff --git a/resources/views/budgets/index.twig b/resources/views/budgets/index.twig index b70e8ca528..0ebfacb9a8 100644 --- a/resources/views/budgets/index.twig +++ b/resources/views/budgets/index.twig @@ -261,6 +261,7 @@ {% if not budgetLimit.in_range %} {{ trans('firefly.budget_limit_not_in_range', {start: budgetLimit.start_date, end: budgetLimit.end_date}) }} +
{% endif %}
diff --git a/resources/views/emails/error-html.twig b/resources/views/emails/error-html.twig index d06b2c3ddc..08c4f1be05 100644 --- a/resources/views/emails/error-html.twig +++ b/resources/views/emails/error-html.twig @@ -1,6 +1,6 @@ {% include 'emails.header-html' %}

- {{ trans('email.error_intro', { version: version, errorMessage: errorMessage })|raw }} + {{ trans('email.error_intro', { version: version, errorMessage: errorMessage|escape })|raw }}

diff --git a/resources/views/layout/default.twig b/resources/views/layout/default.twig index d9e15c5dda..82fbcebedc 100644 --- a/resources/views/layout/default.twig +++ b/resources/views/layout/default.twig @@ -24,7 +24,6 @@ {# CSS things #} - {# the theme #} @@ -41,13 +40,16 @@ ); } + {% endif %} {% if 'dark' == darkMode %} + {% endif %} {% if 'light' == darkMode %} + {% endif %} {# Firefly III customisations #} diff --git a/resources/views/layout/empty.twig b/resources/views/layout/empty.twig index 3220b082fc..10aa6155b1 100644 --- a/resources/views/layout/empty.twig +++ b/resources/views/layout/empty.twig @@ -16,7 +16,6 @@ {# libraries #} - {# the theme #} diff --git a/resources/views/layout/guest.twig b/resources/views/layout/guest.twig index 1c57486734..840880e838 100644 --- a/resources/views/layout/guest.twig +++ b/resources/views/layout/guest.twig @@ -26,7 +26,6 @@ {# libraries #} - {# the theme #} diff --git a/yarn.lock b/yarn.lock index 39d620ffc5..c54f3fc0b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1621,10 +1621,10 @@ autoprefixer@^10.4.0: picocolors "^1.0.0" postcss-value-parser "^4.2.0" -axios@^1.2: - version "1.3.5" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.5.tgz#e07209b39a0d11848e3e341fa087acd71dadc542" - integrity sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw== +axios@^1.3: + version "1.3.6" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.6.tgz#1ace9a9fb994314b5f6327960918406fa92c6646" + integrity sha512-PEcdkk7JcdPiMDkvM4K6ZBRYq9keuVJsToxm2zQIM70Qqo2WHTdJZMXcG9X+RmRp2VPNUQC8W1RAGbgt6b1yMg== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" @@ -3688,7 +3688,7 @@ multicast-dns@^7.2.5: dns-packet "^5.2.2" thunky "^1.0.2" -nanoid@^3.3.4: +nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA== @@ -4269,11 +4269,11 @@ postcss@^7.0.36: source-map "^0.6.1" postcss@^8.1.10, postcss@^8.2.15, postcss@^8.4, postcss@^8.4.14: - version "8.4.21" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.21.tgz#c639b719a57efc3187b13a1d765675485f4134f4" - integrity sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg== + version "8.4.23" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.23.tgz#df0aee9ac7c5e53e1075c24a3613496f9e6552ab" + integrity sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA== dependencies: - nanoid "^3.3.4" + nanoid "^3.3.6" picocolors "^1.0.0" source-map-js "^1.0.2"