From 66c55b7bbe386be3ada9fa55881e2dec284a8845 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 16 Apr 2019 16:20:46 +0200 Subject: [PATCH] Improve tests, models and views. --- .../V1/Controllers/TransactionController.php | 4 +- .../V1/Requests/TransactionStoreRequest.php | 6 +- .../V1/Requests/TransactionUpdateRequest.php | 7 +- .../Upgrade/TransactionIdentifier.php | 5 +- app/Factory/TransactionFactory.php | 2 + .../Transaction/ShowController.php | 71 +- .../Controllers/TransactionController.php | 55 - app/Models/TransactionGroup.php | 4 +- app/Models/TransactionJournal.php | 2 +- app/Providers/FireflyServiceProvider.php | 11 - .../TransactionGroupRepository.php | 89 + .../TransactionGroupRepositoryInterface.php | 28 + app/Support/Cronjobs/RecurringCronjob.php | 2 + app/Support/Twig/Extension/Account.php | 51 - app/Support/Twig/Extension/Transaction.php | 426 - .../Twig/Extension/TransactionJournal.php | 178 - app/Support/Twig/General.php | 44 +- app/Support/Twig/Journal.php | 223 - app/Support/Twig/Loader/AccountLoader.php | 52 - .../Twig/Loader/TransactionJournalLoader.php | 52 - app/Support/Twig/Loader/TransactionLoader.php | 52 - app/Support/Twig/Transaction.php | 55 - .../{Extension => }/TransactionGroupTwig.php | 70 + app/Support/Twig/Translation.php | 32 - .../TransactionGroupTransformer.php | 172 + app/User.php | 4 - app/Validation/AccountValidator.php | 3 + app/Validation/TransactionValidation.php | 130 +- composer.lock | 242 +- public/v1/js/ff/transactions/show.js | 37 +- public/v1/js/lib/vue.js | 11944 ++++++++++++++++ resources/lang/en_US/firefly.php | 5 + resources/lang/en_US/validation.php | 1 + .../v1/accounts/reconcile/transactions.twig | 142 +- resources/views/v1/admin/link/show.twig | 2 +- resources/views/v1/partials/journal-row.twig | 71 +- .../views/v1/partials/transaction-row.twig | 92 +- resources/views/v1/popup/list/journals.twig | 91 +- .../v1/reports/partials/journals-audit.twig | 157 +- resources/views/v1/search/search.twig | 109 +- .../views/v1/transactions/mass/delete.twig | 2 +- resources/views/v1/transactions/show.twig | 499 +- .../Controllers/TransactionControllerTest.php | 3457 +---- tests/TestCase.php | 96 +- 44 files changed, 13710 insertions(+), 5067 deletions(-) delete mode 100644 app/Support/Twig/Extension/Account.php delete mode 100644 app/Support/Twig/Extension/Transaction.php delete mode 100644 app/Support/Twig/Extension/TransactionJournal.php delete mode 100644 app/Support/Twig/Journal.php delete mode 100644 app/Support/Twig/Loader/AccountLoader.php delete mode 100644 app/Support/Twig/Loader/TransactionJournalLoader.php delete mode 100644 app/Support/Twig/Loader/TransactionLoader.php delete mode 100644 app/Support/Twig/Transaction.php rename app/Support/Twig/{Extension => }/TransactionGroupTwig.php (73%) create mode 100644 public/v1/js/lib/vue.js diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index adb2befa11..cd5a332cc8 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -291,7 +291,7 @@ class TransactionController extends Controller $selectedGroup = $collector->getGroups()->first(); if (null === $selectedGroup) { - throw new NotFoundHttpException(); + throw new NotFoundHttpException(); // @codeCoverageIgnore } /** @var TransactionGroupTransformer $transformer */ $transformer = app(TransactionGroupTransformer::class); @@ -335,7 +335,7 @@ class TransactionController extends Controller $selectedGroup = $collector->getGroups()->first(); if (null === $selectedGroup) { - throw new NotFoundHttpException(); + throw new NotFoundHttpException(); // @codeCoverageIgnore } /** @var TransactionGroupTransformer $transformer */ $transformer = app(TransactionGroupTransformer::class); diff --git a/app/Api/V1/Requests/TransactionStoreRequest.php b/app/Api/V1/Requests/TransactionStoreRequest.php index 66c8c8d9ef..c4d1b3a97c 100644 --- a/app/Api/V1/Requests/TransactionStoreRequest.php +++ b/app/Api/V1/Requests/TransactionStoreRequest.php @@ -182,7 +182,7 @@ class TransactionStoreRequest extends Request // the group must have a description if > 1 journal. $this->validateGroupDescription($validator); - // TODO validate that the currency fits the source and/or destination account. + // TODO the currency info must match the accounts involved. } ); @@ -217,8 +217,8 @@ class TransactionStoreRequest extends Request 'foreign_currency_code' => $this->stringFromValue($object['foreign_currency_code']), // amount and foreign amount. Cannot be 0. - 'amount' => $this->stringFromValue($object['amount']), - 'foreign_amount' => $this->stringFromValue($object['foreign_amount']), + 'amount' => $this->stringFromValue((string)$object['amount']), + 'foreign_amount' => $this->stringFromValue((string)$object['foreign_amount']), // description. 'description' => $this->stringFromValue($object['description']), diff --git a/app/Api/V1/Requests/TransactionUpdateRequest.php b/app/Api/V1/Requests/TransactionUpdateRequest.php index 28b4465eca..e2a4a85010 100644 --- a/app/Api/V1/Requests/TransactionUpdateRequest.php +++ b/app/Api/V1/Requests/TransactionUpdateRequest.php @@ -237,13 +237,12 @@ class TransactionUpdateRequest extends Request // all transaction types must be equal: $this->validateTransactionTypesForUpdate($validator); - // if type is set, source + destination info is mandatory. - $this->validateAccountPresence($validator); - // validate source/destination is equal, depending on the transaction journal type. $this->validateEqualAccountsForUpdate($validator, $transactionGroup); + // TODO if type is set, source + destination info is mandatory. // TODO validate that the currency fits the source and/or destination account. + // TODO the currency info must match the accounts involved. // all journals must have a description //$this->validateDescriptions($validator); @@ -308,7 +307,7 @@ class TransactionUpdateRequest extends Request foreach ($this->arrayFields as $fieldName) { if (array_key_exists($fieldName, $transaction)) { - $current[$fieldName] = $this->arrayFromValue((string)$transaction[$fieldName]); + $current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]); } } $return[] = $current; diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php index 212a6dafa3..7fdc561c00 100644 --- a/app/Console/Commands/Upgrade/TransactionIdentifier.php +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -83,12 +83,13 @@ class TransactionIdentifier extends Command ->where('t_count', '>', 2) ->select(['id', 't_count']); $journalIds = array_unique($result->pluck('id')->toArray()); - + $count= 0; foreach ($journalIds as $journalId) { $this->updateJournalidentifiers((int)$journalId); + $count++; } $end = round(microtime(true) - $start, 2); - $this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end)); + $this->info(sprintf('Verified and fixed %d transaction identifiers in %s seconds.', $count, $end)); $this->markAsExecuted(); return 0; diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 7890918d06..90af8a1448 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -124,6 +124,8 @@ class TransactionFactory $sourceAccount = $this->getAccount($type, 'source', (int)$data['source_id'], $data['source_name']); $destinationAccount = $this->getAccount($type, 'destination', (int)$data['destination_id'], $data['destination_name']); + // at this point we know the + $amount = $this->getAmount($data['amount']); $foreignAmount = $this->getForeignAmount($data['foreign_amount']); $one = $this->create($sourceAccount, $currency, app('steam')->negative($amount)); diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index b1646f55c1..e8b1551289 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -1,6 +1,6 @@ middleware( function ($request, $next) { - $this->groupRepository = app(TransactionGroupRepositoryInterface::class); + $this->repository = app(TransactionGroupRepositoryInterface::class); app('view')->share('title', (string)trans('firefly.transactions')); - app('view')->share('mainTitleIcon', 'fa-repeat'); + app('view')->share('mainTitleIcon', 'fa-exchange'); return $next($request); } @@ -64,16 +67,58 @@ class ShowController extends Controller public function show(TransactionGroup $transactionGroup) { /** @var TransactionJournal $first */ - $first = $transactionGroup->transactionJournals->first(); - $groupType = $first->transactionType->type; - $description = $transactionGroup->title; - if ($transactionGroup->transactionJournals()->count() > 1) { - $description = $first->description; + $first = $transactionGroup->transactionJournals->first(); + $splits = $transactionGroup->transactionJournals->count(); + $type = (string)trans(sprintf('firefly.%s', strtolower($first->transactionType->type))); + $title = 1 === $splits ? $first->description : $transactionGroup->title; + $subTitle = sprintf('%s: "%s"', $type, $title); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); + $transformer->setParameters(new ParameterBag); + $groupArray = $transformer->transformObject($transactionGroup); + + // do some amount calculations: + $amounts = []; + foreach ($groupArray['transactions'] as $transaction) { + $symbol = $transaction['currency_symbol']; + if (!isset($amounts[$symbol])) { + $amounts[$symbol] = [ + 'amount' => '0', + 'symbol' => $symbol, + 'decimal_places' => $transaction['currency_decimal_places'], + ]; + } + + $amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], $transaction['amount']); + if (null !== $transaction['foreign_amount']) { + // same for foreign currency: + $foreignSymbol = $transaction['foreign_currency_symbol']; + if (!isset($amounts[$foreignSymbol])) { + $amounts[$foreignSymbol] = [ + 'amount' => '0', + 'symbol' => $foreignSymbol, + 'decimal_places' => $transaction['foreign_currency_decimal_places'], + ]; + } + + $amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], $transaction['foreign_amount']); + } } - $subTitle = sprintf('%s: "%s"', $groupType, $description); + $events = $this->repository->getPiggyEvents($transactionGroup); + $attachments = $this->repository->getAttachments($transactionGroup); + $links = $this->repository->getLinks($transactionGroup); - return view('transactions.show', compact('transactionGroup', 'subTitle')); + // TODO links to other journals, use the API + // TODO links to attachments, use the API. + // TODO links to piggy bank events. + + return view( + 'transactions.show', compact( + 'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray', + 'events', 'attachments', 'links' + ) + ); } - } \ No newline at end of file diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 1da5411663..9fc1b22d5b 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -212,61 +212,6 @@ class TransactionController extends Controller return response()->json([true]); } - /** - * Show a transaction. - * - * @param TransactionJournal $journal - * @param LinkTypeRepositoryInterface $linkTypeRepository - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - * @throws FireflyException - */ - public function show(TransactionJournal $journal, LinkTypeRepositoryInterface $linkTypeRepository) - { - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); - } - $transactionType = $journal->transactionType->type; - if (TransactionType::RECONCILIATION === $transactionType) { - return redirect(route('accounts.reconcile.show', [$journal->id])); // @codeCoverageIgnore - } - $linkTypes = $linkTypeRepository->get(); - $links = $linkTypeRepository->getLinks($journal); - - // get attachments: - $attachments = $this->repository->getAttachments($journal); - $attachments = $attachments->each( - function (Attachment $attachment) { - $attachment->file_exists = $this->attachmentRepository->exists($attachment); - - return $attachment; - } - ); - - // get transactions using the collector: - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); - $set = $collector->getTransactions(); - $transactions = []; - - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); - $transformer->setParameters(new ParameterBag); - - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $transactions[] = $transformer->transform($transaction); - } - - $events = $this->repository->getPiggyBankEvents($journal); - $what = strtolower($transactionType); - $subTitle = trans('firefly.' . $what) . ' "' . $journal->description . '"'; - - return view('transactions.show', compact('journal', 'attachments', 'events', 'subTitle', 'what', 'transactions', 'linkTypes', 'links')); - } } diff --git a/app/Models/TransactionGroup.php b/app/Models/TransactionGroup.php index accad7a5f4..f7a3a4afd6 100644 --- a/app/Models/TransactionGroup.php +++ b/app/Models/TransactionGroup.php @@ -98,7 +98,9 @@ class TransactionGroup extends Model /** @var User $user */ $user = auth()->user(); /** @var TransactionGroup $group */ - $group = $user->transactionGroups()->where('transaction_groups.id', $groupId)->first(['transaction_groups.*']); + $group = $user->transactionGroups() + ->with(['transactionJournals','transactionJournals.transactions']) + ->where('transaction_groups.id', $groupId)->first(['transaction_groups.*']); if (null !== $group) { return $group; } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 3dc91f61c7..9c06690c1b 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -368,7 +368,7 @@ class TransactionJournal extends Model * @codeCoverageIgnore * @return BelongsTo */ - public function transactionGroup(): BelongsToMany + public function transactionGroup(): BelongsTo { return $this->belongsTo(TransactionGroup::class); } diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index e4f3f9b299..38f88ecc49 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -63,13 +63,7 @@ use FireflyIII\Support\Steam; use FireflyIII\Support\Twig\AmountFormat; use FireflyIII\Support\Twig\Extension\TransactionGroupTwig; use FireflyIII\Support\Twig\General; -use FireflyIII\Support\Twig\Journal; -use FireflyIII\Support\Twig\Loader\AccountLoader; -use FireflyIII\Support\Twig\Loader\TransactionGroupLoader; -use FireflyIII\Support\Twig\Loader\TransactionJournalLoader; -use FireflyIII\Support\Twig\Loader\TransactionLoader; use FireflyIII\Support\Twig\Rule; -use FireflyIII\Support\Twig\Transaction; use FireflyIII\Support\Twig\Translation; use FireflyIII\Validation\FireflyValidator; use Illuminate\Foundation\Application; @@ -101,14 +95,9 @@ class FireflyServiceProvider extends ServiceProvider ); $config = app('config'); Twig::addExtension(new Functions($config)); - Twig::addRuntimeLoader(new TransactionLoader); - Twig::addRuntimeLoader(new AccountLoader); - Twig::addRuntimeLoader(new TransactionJournalLoader); Twig::addExtension(new General); Twig::addExtension(new TransactionGroupTwig); - Twig::addExtension(new Journal); Twig::addExtension(new Translation); - Twig::addExtension(new Transaction); Twig::addExtension(new Rule); Twig::addExtension(new AmountFormat); Twig::addExtension(new Twig_Extension_Debug); diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index f046a7b130..160cfd0f76 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -29,11 +29,14 @@ use DB; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionGroupFactory; +use FireflyIII\Models\Attachment; use FireflyIII\Models\Note; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Services\Internal\Update\GroupUpdateService; use FireflyIII\Support\NullArrayObject; +use Illuminate\Database\Eloquent\Builder; /** * Class TransactionGroupRepository @@ -52,6 +55,80 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface } } + /** + * Return all attachments for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getAttachments(TransactionGroup $group): array + { + $journals = $group->transactionJournals->pluck('id')->toArray(); + $set = Attachment::whereIn('attachable_id', $journals) + ->where('attachable_type', TransactionJournal::class) + ->whereNull('deleted_at')->get(); + + $result = []; + /** @var Attachment $attachment */ + foreach ($set as $attachment) { + $current = $attachment->toArray(); + $current['file_exists'] = true; + $current['journal_title'] = $attachment->attachable->description; + $result[] = $current; + + } + + //$result = $set->toArray(); + + + return $result; + } + + /** + * Return all journal links for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getLinks(TransactionGroup $group): array + { + $return = []; + $journals = $group->transactionJournals->pluck('id')->toArray(); + $set = TransactionJournalLink + ::where( + static function (Builder $q) use ($journals) { + $q->whereIn('source_id', $journals); + $q->orWhereIn('destination_id', $journals); + } + ) + ->with(['source', 'destination']) + ->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id') + ->get(['journal_links.*', 'link_types.inward', 'link_types.outward']); + /** @var TransactionJournalLink $entry */ + foreach ($set as $entry) { + $journalId = in_array($entry->source_id, $journals, true) ? $entry->source_id : $entry->destination_id; + $return[$journalId] = $return[$journalId] ?? []; + if ($journalId === $entry->source_id) { + $return[$journalId][] = [ + 'link' => $entry->outward, + 'group' => $entry->destination->transaction_group_id, + 'description' => $entry->destination->description, + ]; + } + if ($journalId === $entry->destination_id) { + $return[$journalId][] = [ + 'link' => $entry->inward, + 'group' => $entry->source->transaction_group_id, + 'description' => $entry->source->description, + ]; + } + } + + return $return; + } + /** * Return object with all found meta field things as Carbon objects. * @@ -124,6 +201,18 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface return $note->text; } + /** + * Return all piggy bank events for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getPiggyEvents(TransactionGroup $group): array + { + return []; + } + /** * Get the tags for a journal (by ID). * diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php index 326fda4bb6..73045993f6 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php @@ -33,6 +33,25 @@ use FireflyIII\User; */ interface TransactionGroupRepositoryInterface { + + /** + * Return all attachments for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getAttachments(TransactionGroup $group): array; + + /** + * Return all journal links for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getLinks(TransactionGroup $group): array; + /** * Return object with all found meta field things as Carbon objects. * @@ -62,6 +81,15 @@ interface TransactionGroupRepositoryInterface */ public function getNoteText(int $journalId): ?string; + /** + * Return all piggy bank events for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getPiggyEvents(TransactionGroup $group): array; + /** * Get the tags for a journal (by ID). * diff --git a/app/Support/Cronjobs/RecurringCronjob.php b/app/Support/Cronjobs/RecurringCronjob.php index 9dea5b326b..c8ebdcde32 100644 --- a/app/Support/Cronjobs/RecurringCronjob.php +++ b/app/Support/Cronjobs/RecurringCronjob.php @@ -28,6 +28,7 @@ use FireflyIII\Exceptions\FireflyException; use FireflyIII\Jobs\CreateRecurringTransactions; use FireflyIII\Models\Configuration; use Log; +use Preferences; /** * Class RecurringCronjob @@ -67,6 +68,7 @@ class RecurringCronjob extends AbstractCronjob Log::error($e->getTraceAsString()); throw new FireflyException(sprintf('Could not run recurring transaction cron job: %s', $e->getMessage())); } + Preferences::mark(); return true; } diff --git a/app/Support/Twig/Extension/Account.php b/app/Support/Twig/Extension/Account.php deleted file mode 100644 index c3e61b32ba..0000000000 --- a/app/Support/Twig/Extension/Account.php +++ /dev/null @@ -1,51 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use FireflyIII\Models\Account as AccountModel; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use Twig_Extension; - -/** - * Class Account. - */ -class Account extends Twig_Extension -{ - /** - * @param AccountModel $account - * @param string $field - * - * @return string - */ - public function getMetaField(AccountModel $account, string $field): string - { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $result = $repository->getMetaValue($account, $field); - if (null === $result) { - return ''; - } - - return $result; - } -} diff --git a/app/Support/Twig/Extension/Transaction.php b/app/Support/Twig/Extension/Transaction.php deleted file mode 100644 index 9137ee1e9f..0000000000 --- a/app/Support/Twig/Extension/Transaction.php +++ /dev/null @@ -1,426 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Attachment; -use FireflyIII\Models\Transaction as TransactionModel; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use Lang; -use Log; -use Twig_Extension; - -/** - * Class Transaction. - */ -class Transaction extends Twig_Extension -{ - /** - * Can show the amount of a transaction, if that transaction has been collected by the journal collector. - * - * @param TransactionModel $transaction - * - * @return string - */ - public function amount(TransactionModel $transaction): string - { - // at this point amount is always negative. - $amount = bcmul(app('steam')->positive((string)$transaction->transaction_amount), '-1'); - $format = '%s'; - $coloured = true; - - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type && 1 === bccomp((string)$transaction->transaction_amount, '0')) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::DEPOSIT === $transaction->transaction_type_type) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::TRANSFER === $transaction->transaction_type_type) { - $amount = app('steam')->positive($amount); - $coloured = false; - $format = '%s'; - } - if (TransactionType::OPENING_BALANCE === $transaction->transaction_type_type) { - $amount = (string)$transaction->transaction_amount; - } - - $currency = new TransactionCurrency; - $currency->symbol = $transaction->transaction_currency_symbol; - $currency->decimal_places = $transaction->transaction_currency_dp; - $str = sprintf($format, app('amount')->formatAnything($currency, $amount, $coloured)); - - if (null !== $transaction->transaction_foreign_amount) { - $amount = bcmul(app('steam')->positive((string)$transaction->transaction_foreign_amount), '-1'); - if (TransactionType::DEPOSIT === $transaction->transaction_type_type) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::TRANSFER === $transaction->transaction_type_type) { - $amount = app('steam')->positive($amount); - $coloured = false; - $format = '%s'; - } - - $currency = new TransactionCurrency; - $currency->symbol = $transaction->foreign_currency_symbol; - $currency->decimal_places = $transaction->foreign_currency_dp; - $str .= ' (' . sprintf($format, app('amount')->formatAnything($currency, $amount, $coloured)) . ')'; - } - - return $str; - } - - /** - * @param array $transaction - * - * @return string - */ - public function amountArray(array $transaction): string - { - // first display amount: - $amount = (string)$transaction['amount']; - $fakeCurrency = new TransactionCurrency; - $fakeCurrency->decimal_places = $transaction['currency_decimal_places']; - $fakeCurrency->symbol = $transaction['currency_symbol']; - $string = app('amount')->formatAnything($fakeCurrency, $amount, true); - - // then display (if present) the foreign amount: - if (null !== $transaction['foreign_amount']) { - $amount = (string)$transaction['foreign_amount']; - $fakeCurrency = new TransactionCurrency; - $fakeCurrency->decimal_places = $transaction['foreign_currency_decimal_places']; - $fakeCurrency->symbol = $transaction['foreign_currency_symbol']; - $string .= ' (' . app('amount')->formatAnything($fakeCurrency, $amount, true) . ')'; - } - - return $string; - } - - /** - * - * @param TransactionModel $transaction - * - * @return string - */ - public function budgets(TransactionModel $transaction): string - { - $txt = ''; - // journal has a budget: - if (null !== $transaction->transaction_journal_budget_id) { - $name = $transaction->transaction_journal_budget_name; - $txt = sprintf('%s', route('budgets.show', [$transaction->transaction_journal_budget_id]), $name, $name); - } - - // transaction has a budget - if (null !== $transaction->transaction_budget_id && '' === $txt) { - $name = $transaction->transaction_budget_name; - $txt = sprintf('%s', route('budgets.show', [$transaction->transaction_budget_id]), $name, $name); - } - - if ('' === $txt) { - // see if the transaction has a budget: - $budgets = $transaction->budgets()->get(); - if (0 === $budgets->count()) { - $budgets = $transaction->transactionJournal()->first()->budgets()->get(); - } - if ($budgets->count() > 0) { - $str = []; - foreach ($budgets as $budget) { - $str[] = sprintf('%s', route('budgets.show', [$budget->id]), $budget->name, $budget->name); - } - $txt = implode(', ', $str); - } - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function categories(TransactionModel $transaction): string - { - $txt = ''; - // journal has a category: - if (null !== $transaction->transaction_journal_category_id) { - $name = $transaction->transaction_journal_category_name; - $txt = sprintf('%s', route('categories.show', [$transaction->transaction_journal_category_id]), $name, $name); - } - - // transaction has a category: - if (null !== $transaction->transaction_category_id && '' === $txt) { - $name = $transaction->transaction_category_name; - $txt = sprintf('%s', route('categories.show', [$transaction->transaction_category_id]), $name, $name); - } - - if ('' === $txt) { - // see if the transaction has a category: - $categories = $transaction->categories()->get(); - if (0 === $categories->count()) { - $categories = $transaction->transactionJournal()->first()->categories()->get(); - } - if ($categories->count() > 0) { - $str = []; - foreach ($categories as $category) { - $str[] = sprintf('%s', route('categories.show', [$category->id]), $category->name, $category->name); - } - - $txt = implode(', ', $str); - } - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function description(TransactionModel $transaction): string - { - $description = $transaction->description; - if ('' !== (string)$transaction->transaction_description) { - $description = $transaction->transaction_description . ' (' . $transaction->description . ')'; - } - - return $description; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function destinationAccount(TransactionModel $transaction): string - { - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) { - return '—'; - } - - $name = $transaction->account_name; - $iban = $transaction->account_iban; - $transactionId = (int)$transaction->account_id; - $type = $transaction->account_type; - - // name is present in object, use that one: - if (null !== $transaction->opposing_account_id && bccomp($transaction->transaction_amount, '0') === -1) { - $name = $transaction->opposing_account_name; - $transactionId = (int)$transaction->opposing_account_id; - $type = $transaction->opposing_account_type; - $iban = $transaction->opposing_account_iban; - } - - // Find the opposing account and use that one: - if (null === $transaction->opposing_account_id && bccomp($transaction->transaction_amount, '0') === -1) { - // if the amount is negative, find the opposing account and use that one: - $journalId = $transaction->journal_id; - /** @var TransactionModel $other */ - $other = TransactionModel - ::where('transaction_journal_id', $journalId) - ->where('transactions.id', '!=', $transaction->id) - ->where('amount', '=', bcmul($transaction->transaction_amount, '-1')) - ->where('identifier', $transaction->identifier) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - if (null === $other) { - Log::error(sprintf('Cannot find other transaction for journal #%d', $journalId)); - - return ''; - } - $name = $other->name; - $transactionId = $other->account_id; - $type = $other->type; - } - - if (AccountType::CASH === $type) { - $txt = '(' . trans('firefly.cash') . ')'; - - return $txt; - } - - $txt = sprintf('%1$s', e($name), route('accounts.show', [$transactionId]), $iban); - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function hasAttachments(TransactionModel $transaction): string - { - $res = ''; - if (\is_int($transaction->attachmentCount) && $transaction->attachmentCount > 0) { - $res = sprintf( - '', Lang::choice( - 'firefly.nr_of_attachments', - $transaction->attachmentCount, ['count' => $transaction->attachmentCount] - ) - ); - } - if (null === $transaction->attachmentCount) { - $journalId = (int)$transaction->journal_id; - $count = Attachment::whereNull('deleted_at') - ->where('attachable_type', TransactionJournal::class) - ->where('attachable_id', $journalId) - ->count(); - if ($count > 0) { - $res = sprintf('', Lang::choice('firefly.nr_of_attachments', $count, ['count' => $count])); - } - } - - return $res; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function icon(TransactionModel $transaction): string - { - switch ($transaction->transaction_type_type) { - case TransactionType::WITHDRAWAL: - $txt = sprintf('', (string)trans('firefly.withdrawal')); - break; - case TransactionType::DEPOSIT: - $txt = sprintf('', (string)trans('firefly.deposit')); - break; - case TransactionType::TRANSFER: - $txt = sprintf('', (string)trans('firefly.transfer')); - break; - case TransactionType::OPENING_BALANCE: - $txt = sprintf('', (string)trans('firefly.opening_balance')); - break; - case TransactionType::RECONCILIATION: - $txt = sprintf('', (string)trans('firefly.reconciliation_transaction')); - break; - default: - $txt = ''; - break; - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function isReconciled(TransactionModel $transaction): string - { - $icon = ''; - if (1 === (int)$transaction->reconciled) { - $icon = ''; - } - - return $icon; - } - - /** - * Returns an icon when the transaction is a split transaction. - * - * @param TransactionModel $transaction - * - * @return string - */ - public function isSplit(TransactionModel $transaction): string - { - $res = ''; - if (true === $transaction->is_split) { - $res = ''; - } - - if (null === $transaction->is_split) { - $journalId = (int)$transaction->journal_id; - $count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count(); - if ($count > 2) { - $res = ''; - } - } - - return $res; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function sourceAccount(TransactionModel $transaction): string - { - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) { - return '—'; - } - - // if the amount is negative, assume that the current account (the one in $transaction) is indeed the source account. - $name = $transaction->account_name; - $transactionId = (int)$transaction->account_id; - $type = $transaction->account_type; - $iban = $transaction->account_iban; - - // name is present in object, use that one: - if (null !== $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) { - $name = $transaction->opposing_account_name; - $transactionId = (int)$transaction->opposing_account_id; - $type = $transaction->opposing_account_type; - $iban = $transaction->opposing_account_iban; - } - // Find the opposing account and use that one: - if (null === $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) { - $journalId = $transaction->journal_id; - /** @var TransactionModel $other */ - $other = TransactionModel::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id) - ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where( - 'identifier', - $transaction->identifier - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - $name = $other->name; - $transactionId = $other->account_id; - $type = $other->type; - } - - if (AccountType::CASH === $type) { - $txt = '(' . trans('firefly.cash') . ')'; - - return $txt; - } - - $txt = sprintf('%1$s', e($name), route('accounts.show', [$transactionId]), $iban); - - return $txt; - } -} diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php deleted file mode 100644 index 4e1ee4be73..0000000000 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ /dev/null @@ -1,178 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use Carbon\Carbon; -use FireflyIII\Models\Transaction as TransactionModel; -use FireflyIII\Models\TransactionJournal as JournalModel; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use Twig_Extension; - -/** - * Class TransactionJournal - */ -class TransactionJournal extends Twig_Extension -{ - /** - * @param JournalModel $journal - * @param string $field - * - * @return null|Carbon - */ - public function getMetaDate(JournalModel $journal, string $field): ?Carbon - { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - return $repository->getMetaDate($journal, $field); - } - - /** - * @param JournalModel $journal - * @param string $field - * - * @return string - */ - public function getMetaField(JournalModel $journal, string $field): string - { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $result = $repository->getMetaField($journal, $field); - if (null === $result) { - return ''; - } - - return $result; - } - - /** - * Return if journal HAS field. - * - * @param JournalModel $journal - * @param string $field - * - * @return bool - */ - public function hasMetaField(JournalModel $journal, string $field): bool - { - // HIER BEN JE - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $result = $repository->getMetaField($journal, $field); - if (null === $result) { - return false; - } - if ('' === (string)$result) { - return false; - } - - return true; - } - - /** - * @param JournalModel $journal - * - * @return string - */ - public function totalAmount(JournalModel $journal): string - { - $type = $journal->transactionType->type; - $totals = $this->getTotalAmount($journal); - $array = []; - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - if (null !== $total['currency']) { - $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); - } - } - - return implode(' / ', $array); - } - - /** - * @param JournalModel $journal - * - * @return string - */ - public function totalAmountPlain(JournalModel $journal): string - { - $type = $journal->transactionType->type; - $totals = $this->getTotalAmount($journal); - $array = []; - - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - $array[] = app('amount')->formatAnything($total['currency'], $total['amount'], false); - } - - return implode(' / ', $array); - } - - /** - * @param JournalModel $journal - * - * @return array - */ - private function getTotalAmount(JournalModel $journal): array - { - $transactions = $journal->transactions()->where('amount', '>', 0)->get(); - $totals = []; - - /** @var TransactionModel $transaction */ - foreach ($transactions as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $currency = $transaction->transactionCurrency; - - if (!isset($totals[$currencyId])) { - $totals[$currencyId] = [ - 'amount' => '0', - 'currency' => $currency, - ]; - } - $totals[$currencyId]['amount'] = bcadd($transaction->amount, $totals[$currencyId]['amount']); - - if (null !== $transaction->foreign_currency_id) { - $foreignAmount = $transaction->foreign_amount ?? '0'; - $foreignId = $transaction->foreign_currency_id; - $foreign = $transaction->foreignCurrency; - if (!isset($totals[$foreignId])) { - $totals[$foreignId] = [ - 'amount' => '0', - 'currency' => $foreign, - ]; - } - $totals[$foreignId]['amount'] = bcadd( - $foreignAmount, - $totals[$foreignId]['amount'] - ); - } - } - - return $totals; - } -} diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 265db54739..eb59b9783b 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -24,8 +24,8 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use FireflyIII\Models\Account; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; -use FireflyIII\Support\Twig\Extension\Account as AccountExtension; use League\CommonMark\CommonMarkConverter; use Route; use Twig_Extension; @@ -56,14 +56,12 @@ class General extends Twig_Extension public function getFunctions(): array { return [ - $this->getCurrencyCode(), - $this->getCurrencySymbol(), $this->phpdate(), $this->activeRouteStrict(), $this->activeRoutePartial(), $this->activeRoutePartialWhat(), $this->formatDate(), - new Twig_SimpleFunction('accountGetMetaField', [AccountExtension::class, 'getMetaField']), + $this->getMetaField(), $this->hasRole(), ]; } @@ -102,7 +100,7 @@ class General extends Twig_Extension return new Twig_SimpleFunction( 'activeRoutePartialWhat', function ($context): string { - [, $route, $what] = \func_get_args(); + [, $route, $what] = func_get_args(); $activeWhat = $context['what'] ?? false; if ($what === $activeWhat && !(false === stripos(Route::getCurrentRoute()->getName(), $route))) { @@ -139,6 +137,8 @@ class General extends Twig_Extension } /** + * Show account balance. Only used on the front page of Firefly III. + * * @return Twig_SimpleFilter */ protected function balance(): Twig_SimpleFilter @@ -158,6 +158,8 @@ class General extends Twig_Extension } /** + * Formats a string as a thing by converting it to a Carbon first. + * * @return Twig_SimpleFunction */ protected function formatDate(): Twig_SimpleFunction @@ -173,6 +175,8 @@ class General extends Twig_Extension } /** + * Used to convert 1024 to 1kb etc. + * * @return Twig_SimpleFilter */ protected function formatFilesize(): Twig_SimpleFilter @@ -198,25 +202,19 @@ class General extends Twig_Extension /** * @return Twig_SimpleFunction */ - protected function getCurrencyCode(): Twig_SimpleFunction + protected function getMetaField(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'getCurrencyCode', - function (): string { - return app('amount')->getCurrencyCode(); - } - ); - } + 'accountGetMetaField', + static function (Account $account, string $field): string { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $result = $repository->getMetaValue($account, $field); + if (null === $result) { + return ''; + } - /** - * @return Twig_SimpleFunction - */ - protected function getCurrencySymbol(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'getCurrencySymbol', - function (): string { - return app('amount')->getCurrencySymbol(); + return $result; } ); } @@ -257,6 +255,8 @@ class General extends Twig_Extension } /** + * Show icon with attachment. + * * @return Twig_SimpleFilter */ protected function mimeIcon(): Twig_SimpleFilter @@ -334,6 +334,8 @@ class General extends Twig_Extension } /** + * Basic example thing for some views. + * * @return Twig_SimpleFunction */ protected function phpdate(): Twig_SimpleFunction diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php deleted file mode 100644 index 632c80a2b3..0000000000 --- a/app/Support/Twig/Journal.php +++ /dev/null @@ -1,223 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Category; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Support\CacheProperties; -use FireflyIII\Support\Twig\Extension\TransactionJournal as TransactionJournalExtension; -use Twig_Extension; -use Twig_SimpleFilter; -use Twig_SimpleFunction; - -/** - * Class Journal. - */ -class Journal extends Twig_Extension -{ - /** - * @return Twig_SimpleFunction - */ - public function getDestinationAccount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'destinationAccount', - function (TransactionJournal $journal) { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('destination-account-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $list = $repository->getJournalDestinationAccounts($journal); - $array = []; - /** @var Account $entry */ - foreach ($list as $entry) { - if (AccountType::CASH === $entry->accountType->type) { - $array[] = '(cash)'; - continue; - } - $array[] = sprintf('%1$s', e($entry->name), route('accounts.show', $entry->id)); - } - $array = array_unique($array); - $result = implode(', ', $array); - $cache->store($result); - - return $result; - } - ); - } - - /** - * @return array - */ - public function getFilters(): array - { - $filters = [ - new Twig_SimpleFilter('journalTotalAmount', [TransactionJournalExtension::class, 'totalAmount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('journalTotalAmountPlain', [TransactionJournalExtension::class, 'totalAmountPlain'], ['is_safe' => ['html']]), - ]; - - return $filters; - } - - /** - * @return array - */ - public function getFunctions(): array - { - $functions = [ - $this->getSourceAccount(), - $this->getDestinationAccount(), - $this->journalBudgets(), - $this->journalCategories(), - new Twig_SimpleFunction('journalGetMetaField', [TransactionJournalExtension::class, 'getMetaField']), - new Twig_SimpleFunction('journalHasMeta', [TransactionJournalExtension::class, 'hasMetaField']), - new Twig_SimpleFunction('journalGetMetaDate', [TransactionJournalExtension::class, 'getMetaDate']), - ]; - - return $functions; - } - - /** - * @return Twig_SimpleFunction - */ - public function getSourceAccount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'sourceAccount', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('source-account-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - $list = $repository->getJournalSourceAccounts($journal); - $array = []; - /** @var Account $entry */ - foreach ($list as $entry) { - if (AccountType::CASH === $entry->accountType->type) { - $array[] = '(cash)'; - continue; - } - $array[] = sprintf('%1$s', e($entry->name), route('accounts.show', $entry->id)); - } - $array = array_unique($array); - $result = implode(', ', $array); - $cache->store($result); - - return $result; - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function journalBudgets(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'journalBudgets', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('budget-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - $budgets = []; - // get all budgets: - foreach ($journal->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); - } - // and more! - foreach ($journal->transactions as $transaction) { - foreach ($transaction->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); - } - } - $string = implode(', ', array_unique($budgets)); - $cache->store($string); - - return $string; - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function journalCategories(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'journalCategories', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('category-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - $categories = []; - // get all categories for the journal itself (easy): - foreach ($journal->categories as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); - } - if (0 === \count($categories)) { - $set = Category::distinct()->leftJoin('category_transaction', 'categories.id', '=', 'category_transaction.category_id') - ->leftJoin('transactions', 'category_transaction.transaction_id', '=', 'transactions.id') - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('categories.user_id', $journal->user_id) - ->where('transaction_journals.id', $journal->id) - ->whereNull('transactions.deleted_at') - ->get(['categories.*']); - /** @var Category $category */ - foreach ($set as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); - } - } - - $string = implode(', ', array_unique($categories)); - $cache->store($string); - - return $string; - } - ); - } -} diff --git a/app/Support/Twig/Loader/AccountLoader.php b/app/Support/Twig/Loader/AccountLoader.php deleted file mode 100644 index be92e6586d..0000000000 --- a/app/Support/Twig/Loader/AccountLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\Account; -use Twig_RuntimeLoaderInterface; - -/** - * Class AccountLoader. - */ -class AccountLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (Account::class === $class) { - return app(Account::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Loader/TransactionJournalLoader.php b/app/Support/Twig/Loader/TransactionJournalLoader.php deleted file mode 100644 index 5268fa9dd8..0000000000 --- a/app/Support/Twig/Loader/TransactionJournalLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\TransactionJournal; -use Twig_RuntimeLoaderInterface; - -/** - * Class TransactionJournalLoader. - */ -class TransactionJournalLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (TransactionJournal::class === $class) { - return app(TransactionJournal::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Loader/TransactionLoader.php b/app/Support/Twig/Loader/TransactionLoader.php deleted file mode 100644 index 43ed1b2774..0000000000 --- a/app/Support/Twig/Loader/TransactionLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\Transaction; -use Twig_RuntimeLoaderInterface; - -/** - * Class TransactionLoader. - */ -class TransactionLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (Transaction::class === $class) { - return app(Transaction::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php deleted file mode 100644 index ffe6b046fd..0000000000 --- a/app/Support/Twig/Transaction.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use FireflyIII\Support\Twig\Extension\Transaction as TransactionExtension; -use Twig_Extension; -use Twig_SimpleFilter; - -/** - * Class Transaction. - */ -class Transaction extends Twig_Extension -{ - /** - * @return array - */ - public function getFilters(): array - { - $filters = [ - new Twig_SimpleFilter('transactionIcon', [TransactionExtension::class, 'icon'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionDescription', [TransactionExtension::class, 'description']), - new Twig_SimpleFilter('transactionIsSplit', [TransactionExtension::class, 'isSplit'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionReconciled', [TransactionExtension::class, 'isReconciled'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionHasAtt', [TransactionExtension::class, 'hasAttachments'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionAmount', [TransactionExtension::class, 'amount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionArrayAmount', [TransactionExtension::class, 'amountArray'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionBudgets', [TransactionExtension::class, 'budgets'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionCategories', [TransactionExtension::class, 'categories'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionSourceAccount', [TransactionExtension::class, 'sourceAccount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionDestinationAccount', [TransactionExtension::class, 'destinationAccount'], ['is_safe' => ['html']]), - ]; - - return $filters; - } -} diff --git a/app/Support/Twig/Extension/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php similarity index 73% rename from app/Support/Twig/Extension/TransactionGroupTwig.php rename to app/Support/Twig/TransactionGroupTwig.php index ca6de28ebf..2ff3ad03dd 100644 --- a/app/Support/Twig/Extension/TransactionGroupTwig.php +++ b/app/Support/Twig/TransactionGroupTwig.php @@ -23,6 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Support\Twig\Extension; +use Carbon\Carbon; +use DB; use FireflyIII\Models\TransactionType; use Twig_Extension; use Twig_SimpleFunction; @@ -40,6 +42,9 @@ class TransactionGroupTwig extends Twig_Extension return [ $this->transactionAmount(), $this->groupAmount(), + $this->journalHasMeta(), + $this->journalGetMetaDate(), + $this->journalGetMetaField() ]; } @@ -64,6 +69,71 @@ class TransactionGroupTwig extends Twig_Extension ); } + /** + * @return Twig_SimpleFunction + */ + public function journalGetMetaDate(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalGetMetaDate', + static function (int $journalId, string $metaField) { + + $entry = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first(); + if (null === $entry) { + return new Carbon; + } + + return new Carbon(json_decode($entry->data, false)); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function journalGetMetaField(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalGetMetaField', + static function (int $journalId, string $metaField) { + + $entry = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first(); + if (null === $entry) { + return ''; + } + + return json_decode($entry->data, true); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function journalHasMeta(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalHasMeta', + static function (int $journalId, string $metaField) { + $count = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->count(); + + return 1 === $count; + } + ); + } + /** * @return Twig_SimpleFunction */ diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php index 6aa55ceec3..05985bde61 100644 --- a/app/Support/Twig/Translation.php +++ b/app/Support/Twig/Translation.php @@ -48,36 +48,4 @@ class Translation extends Twig_Extension return $filters; } - - /** - * {@inheritdoc} - */ - public function getFunctions(): array - { - return [ - $this->journalLinkTranslation(), - ]; - } - - - /** - * @return Twig_SimpleFunction - */ - public function journalLinkTranslation(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'journalLinkTranslation', - function (string $direction, string $original) { - $key = sprintf('firefly.%s_%s', $original, $direction); - $translation = trans($key); - - if ($key === $translation) { - return $original; - } - - return $translation; - }, - ['is_safe' => ['html']] - ); - } } diff --git a/app/Transformers/TransactionGroupTransformer.php b/app/Transformers/TransactionGroupTransformer.php index 1ae45535af..b8a39b815d 100644 --- a/app/Transformers/TransactionGroupTransformer.php +++ b/app/Transformers/TransactionGroupTransformer.php @@ -23,9 +23,15 @@ declare(strict_types=1); namespace FireflyIII\Transformers; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Support\NullArrayObject; +use Illuminate\Support\Collection; /** * Class TransactionGroupTransformer @@ -88,6 +94,172 @@ class TransactionGroupTransformer extends AbstractTransformer return $result; } + /** + * @param TransactionGroup $group + * + * @return array + */ + public function transformObject(TransactionGroup $group): array + { + //$first = $group->transactionJournals->first(); + $result = [ + 'id' => (int)$group->id, + 'created_at' => $group->created_at->toAtomString(), + 'updated_at' => $group->updated_at->toAtomString(), + 'user' => (int)$group->user_id, + 'group_title' => $group->title, + 'transactions' => $this->transformJournals($group->transactionJournals), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/transactions/' . $group->id, + ], + ], + ]; + + // do something else. + + return $result; + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getDestinationTransaction(TransactionJournal $journal): Transaction + { + + return $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount > 0; + } + ); + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + + return $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount < 0; + } + ); + } + + /** + * @param Collection $transactionJournals + * + * @return array + */ + private function transformJournals(Collection $transactionJournals): array + { + $result = []; + /** @var TransactionJournal $journal */ + foreach ($transactionJournals as $journal) { + $source = $this->getSourceTransaction($journal); + $destination = $this->getDestinationTransaction($journal); + $type = $journal->transactionType->type; + + // get amount + $amount = app('steam')->positive($source->amount); + if (TransactionType::WITHDRAWAL !== $type) { + $amount = app('steam')->negative($source->amount); + } + + // get foreign amount: + $foreignAmount = null; + if (null !== $source->foreign_amount) { + $foreignAmount = TransactionType::WITHDRAWAL !== $type + ? app('steam')->positive($source->foreign_amount) + : app('steam')->negative($source->foreign_amount); + } + + $metaFieldData = $this->groupRepos->getMetaFields($journal->id, $this->metaFields); + $metaDateData = $this->groupRepos->getMetaDateFields($journal->id, $this->metaDateFields); + /** @var Budget $budget */ + $budget = $journal->budgets->first(); + /** @var Category $category */ + $category = $journal->categories->first(); + $currency = $source->transactionCurrency; + $result[] = [ + 'user' => (int)$journal->user_id, + 'transaction_journal_id' => $journal->id, + 'type' => strtolower($type), + 'date' => $journal->date->toAtomString(), + 'order' => $journal->order, + + 'currency_id' => $currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + + 'foreign_currency_id' => $source->foreignCurrency ? $source->foreignCurrency->id : null, + 'foreign_currency_code' => $source->foreignCurrency ? $source->foreignCurrency->code : null, + 'foreign_currency_symbol' => $source->foreignCurrency ? $source->foreignCurrency->symbol : null, + 'foreign_currency_decimal_places' => $source->foreignCurrency ? $source->foreignCurrency->decimal_places : null, + + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + + 'description' => $journal->description, + + 'source_id' => $source->account_id, + 'source_name' => $source->account->name, + 'source_iban' => $source->account->iban, + 'source_type' => $source->account->accountType->type, + + 'destination_id' => $destination->account_id, + 'destination_name' => $destination->account->name, + 'destination_iban' => $destination->account->iban, + 'destination_type' => $destination->account->accountType->type, + + 'budget_id' => $budget ? $budget->id : null, + 'budget_name' => $budget ? $budget->name : null, + + 'category_id' => $category ? $category->id : null, + 'category_name' => $category ? $category->name : null, + + 'bill_id' => $journal->bill_id ?: null, + 'bill_name' => $journal->bill_id ? $journal->bill->name : null, + + 'reconciled' => $source->reconciled, + 'notes' => $this->groupRepos->getNoteText($journal->id), + 'tags' => $this->groupRepos->getTags($journal->id), + + 'internal_reference' => $metaFieldData['internal_reference'], + 'external_id' => $metaFieldData['external_id'], + 'original_source' => $metaFieldData['original_source'], + 'recurrence_id' => $metaFieldData['recurrence_id'], + 'bunq_payment_id' => $metaFieldData['bunq_payment_id'], + 'import_hash_v2' => $metaFieldData['import_hash_v2'], + + 'sepa_cc' => $metaFieldData['sepa_cc'], + 'sepa_ct_op' => $metaFieldData['sepa_ct_op'], + 'sepa_ct_id' => $metaFieldData['sepa_ct_id'], + 'sepa_db' => $metaFieldData['sepa_ddb'], + 'sepa_country' => $metaFieldData['sepa_country'], + 'sepa_ep' => $metaFieldData['sepa_ep'], + 'sepa_ci' => $metaFieldData['sepa_ci'], + 'sepa_batch_id' => $metaFieldData['sepa_batch_id'], + + 'interest_date' => $metaDateData['interest_date'] ? $metaDateData['interest_date']->toAtomString() : null, + 'book_date' => $metaDateData['book_date'] ? $metaDateData['book_date']->toAtomString() : null, + 'process_date' => $metaDateData['process_date'] ? $metaDateData['process_date']->toAtomString() : null, + 'due_date' => $metaDateData['due_date'] ? $metaDateData['due_date']->toAtomString() : null, + 'payment_date' => $metaDateData['payment_date'] ? $metaDateData['payment_date']->toAtomString() : null, + 'invoice_date' => $metaDateData['invoice_date'] ? $metaDateData['invoice_date']->toAtomString() : null, + ]; + } + + return $result; + } + /** * @param NullArrayObject $data * diff --git a/app/User.php b/app/User.php index 5c61f19cf4..41092e5011 100644 --- a/app/User.php +++ b/app/User.php @@ -68,11 +68,8 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool blocked * @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $updated_at - * @property string $password * @property string|null $remember_token * @property string|null $reset - * @property bool $blocked - * @property string|null $blocked_code * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Account[] $accounts * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\AvailableBudget[] $availableBudgets @@ -87,7 +84,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBank[] $piggyBanks * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Preference[] $preferences * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Recurrence[] $recurrences - * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Role[] $roles * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\RuleGroup[] $ruleGroups * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Rule[] $rules * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Tag[] $tags diff --git a/app/Validation/AccountValidator.php b/app/Validation/AccountValidator.php index 2ec1d95ab6..88f7f2b5df 100644 --- a/app/Validation/AccountValidator.php +++ b/app/Validation/AccountValidator.php @@ -65,6 +65,9 @@ class AccountValidator $this->combinations = config('firefly.source_dests'); /** @var AccountRepositoryInterface accountRepository */ $this->accountRepository = app(AccountRepositoryInterface::class); + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } } /** diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 5904f6ef8b..ec6dc1e137 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -110,15 +110,20 @@ trait TransactionValidation $data = $validator->getData(); $transactions = $data['transactions'] ?? []; foreach ($transactions as $index => $transaction) { - // must have currency info. - if (isset($transaction['foreign_amount']) - && !(isset($transaction['foreign_currency_id']) - || isset($transaction['foreign_currency_code']))) { + // if foreign amount is present, then the currency must be as well. + if (isset($transaction['foreign_amount']) && !(isset($transaction['foreign_currency_id']) || isset($transaction['foreign_currency_code']))) { $validator->errors()->add( 'transactions.' . $index . '.foreign_amount', (string)trans('validation.require_currency_info') ); } + // if the currency is present, then the amount must be present as well. + if ((isset($transaction['foreign_currency_id']) || isset($transaction['foreign_currency_code'])) && !isset($transaction['foreign_amount'])) { + $validator->errors()->add( + 'transactions.' . $index . '.foreign_amount', + (string)trans('validation.require_currency_amount') + ); + } } } @@ -196,16 +201,6 @@ trait TransactionValidation } } - /** - * If type is set, source + destination info is mandatory. - * - * @param Validator $validator - */ - protected function validateAccountPresence(Validator $validator): void - { - // TODO - } - /** * @param Validator $validator */ @@ -299,113 +294,12 @@ trait TransactionValidation return; } foreach ($transactions as $index => $transaction) { - $journalId = (int)($transaction['transaction_journal_id'] ?? 0); + $journalId = $transaction['transaction_journal_id'] ?? null; + $journalId = null === $journalId ? null : (int)$journalId; $count = $transactionGroup->transactionJournals()->where('id', $journalId)->count(); - if (0 === $journalId || 0 === $count) { + if (null === $journalId || (null !== $journalId && 0 !== $journalId && 0 === $count)) { $validator->errors()->add(sprintf('transactions.%d.source_name', $index), (string)trans('validation.need_id_in_edit')); } } } - - // /** - // * Throws an error when this asset account is invalid. - // * - // * @noinspection MoreThanThreeArgumentsInspection - // * - // * @param Validator $validator - // * @param int|null $accountId - // * @param null|string $accountName - // * @param string $idField - // * @param string $nameField - // * - // * @return null|Account - // */ - // protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account - // { - // /** @var User $admin */ - // $admin = auth()->user(); - // $accountId = (int)$accountId; - // $accountName = (string)$accountName; - // // both empty? hard exit. - // if ($accountId < 1 && '' === $accountName) { - // $validator->errors()->add($idField, (string)trans('validation.filled', ['attribute' => $idField])); - // - // return null; - // } - // // ID belongs to user and is asset account: - // /** @var AccountRepositoryInterface $repository */ - // $repository = app(AccountRepositoryInterface::class); - // $repository->setUser($admin); - // $set = $repository->getAccountsById([$accountId]); - // Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count())); - // if (1 === $set->count()) { - // /** @var Account $first */ - // $first = $set->first(); - // if ($first->accountType->type !== AccountType::ASSET) { - // $validator->errors()->add($idField, (string)trans('validation.belongs_user')); - // - // return null; - // } - // - // // we ignore the account name at this point. - // return $first; - // } - // - // $account = $repository->findByName($accountName, [AccountType::ASSET]); - // if (null === $account) { - // $validator->errors()->add($nameField, (string)trans('validation.belongs_user')); - // - // return null; - // } - // - // return $account; - // } - // - // /** - // * Throws an error when the given opposing account (of type $type) is invalid. - // * Empty data is allowed, system will default to cash. - // * - // * @noinspection MoreThanThreeArgumentsInspection - // * - // * @param Validator $validator - // * @param string $type - // * @param int|null $accountId - // * @param null|string $accountName - // * @param string $idField - // * - // * @return null|Account - // */ - // protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account - // { - // /** @var User $admin */ - // $admin = auth()->user(); - // $accountId = (int)$accountId; - // $accountName = (string)$accountName; - // // both empty? done! - // if ($accountId < 1 && '' === $accountName) { - // return null; - // } - // if (0 !== $accountId) { - // // ID belongs to user and is $type account: - // /** @var AccountRepositoryInterface $repository */ - // $repository = app(AccountRepositoryInterface::class); - // $repository->setUser($admin); - // $set = $repository->getAccountsById([$accountId]); - // if (1 === $set->count()) { - // /** @var Account $first */ - // $first = $set->first(); - // if ($first->accountType->type !== $type) { - // $validator->errors()->add($idField, (string)trans('validation.belongs_user')); - // - // return null; - // } - // - // // we ignore the account name at this point. - // return $first; - // } - // } - // - // // not having an opposing account by this name is NOT a problem. - // return null; - // } } diff --git a/composer.lock b/composer.lock index 8eca583c96..2087eb1038 100644 --- a/composer.lock +++ b/composer.lock @@ -8,32 +8,28 @@ "packages": [ { "name": "adldap2/adldap2", - "version": "v10.0.6", + "version": "v9.1.6", "source": { "type": "git", "url": "https://github.com/Adldap2/Adldap2.git", - "reference": "efbf25c80861e47a5443d176dfa1a640000e8a20" + "reference": "d50204d3eff587957b4bb9d7382d2eda5009ed16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/efbf25c80861e47a5443d176dfa1a640000e8a20", - "reference": "efbf25c80861e47a5443d176dfa1a640000e8a20", + "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/d50204d3eff587957b4bb9d7382d2eda5009ed16", + "reference": "d50204d3eff587957b4bb9d7382d2eda5009ed16", "shasum": "" }, "require": { "ext-ldap": "*", "illuminate/contracts": "~5.0", "php": ">=7.0", - "psr/log": "~1.1", "tightenco/collect": "~5.0" }, "require-dev": { "mockery/mockery": "~1.0", "phpunit/phpunit": "~6.0" }, - "suggest": { - "ext-fileinfo": "fileinfo is required when retrieving user encoded thumbnails" - }, "type": "library", "autoload": { "psr-4": { @@ -61,24 +57,24 @@ "ldap", "windows" ], - "time": "2019-03-22T18:25:32+00:00" + "time": "2019-04-03T19:41:38+00:00" }, { "name": "adldap2/adldap2-laravel", - "version": "v5.1.2", + "version": "v5.1.3", "source": { "type": "git", "url": "https://github.com/Adldap2/Adldap2-Laravel.git", - "reference": "31f80dfad6950f80698986e91cb65eb0c441f182" + "reference": "1a8843b07b389ce26f6d122d19f9443224926d82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adldap2/Adldap2-Laravel/zipball/31f80dfad6950f80698986e91cb65eb0c441f182", - "reference": "31f80dfad6950f80698986e91cb65eb0c441f182", + "url": "https://api.github.com/repos/Adldap2/Adldap2-Laravel/zipball/1a8843b07b389ce26f6d122d19f9443224926d82", + "reference": "1a8843b07b389ce26f6d122d19f9443224926d82", "shasum": "" }, "require": { - "adldap2/adldap2": "^10.0", + "adldap2/adldap2": "^9.0", "php": ">=7.1" }, "require-dev": { @@ -114,7 +110,7 @@ "laravel", "ldap" ], - "time": "2019-02-27T07:09:43+00:00" + "time": "2019-04-02T23:04:08+00:00" }, { "name": "bacon/bacon-qr-code", @@ -984,16 +980,16 @@ }, { "name": "erusev/parsedown", - "version": "v1.7.2", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf" + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/d60bcdc46978357759ecb13cb4b078da783f8faf", - "reference": "d60bcdc46978357759ecb13cb4b078da783f8faf", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", "shasum": "" }, "require": { @@ -1026,7 +1022,7 @@ "markdown", "parser" ], - "time": "2019-03-17T17:19:46+00:00" + "time": "2019-03-17T18:48:37+00:00" }, { "name": "fideloper/proxy", @@ -1313,16 +1309,16 @@ }, { "name": "laravel/framework", - "version": "v5.8.9", + "version": "v5.8.11", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "2455d21938e9072e5a8ecef64ae162a1825d336b" + "reference": "a2cf7a7983329d63edc6fde43142b232bb61aa0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/2455d21938e9072e5a8ecef64ae162a1825d336b", - "reference": "2455d21938e9072e5a8ecef64ae162a1825d336b", + "url": "https://api.github.com/repos/laravel/framework/zipball/a2cf7a7983329d63edc6fde43142b232bb61aa0a", + "reference": "a2cf7a7983329d63edc6fde43142b232bb61aa0a", "shasum": "" }, "require": { @@ -1456,7 +1452,7 @@ "framework", "laravel" ], - "time": "2019-04-02T14:11:17+00:00" + "time": "2019-04-10T13:05:18+00:00" }, { "name": "laravel/passport", @@ -1656,34 +1652,35 @@ }, { "name": "league/commonmark", - "version": "0.18.5", + "version": "0.19.1", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "f94e18d68260f43a7d846279cad88405854b1306" + "reference": "d42b2d4a5d0a8eb2a4514f828ecb6f5db13a7c6f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/f94e18d68260f43a7d846279cad88405854b1306", - "reference": "f94e18d68260f43a7d846279cad88405854b1306", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d42b2d4a5d0a8eb2a4514f828ecb6f5db13a7c6f", + "reference": "d42b2d4a5d0a8eb2a4514f828ecb6f5db13a7c6f", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=5.6.5" + "php": "^7.1" }, "replace": { "colinodell/commonmark-php": "*" }, "require-dev": { "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.28", + "commonmark/commonmark.js": "0.29.0", "erusev/parsedown": "~1.0", "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.2", - "phpunit/phpunit": "^5.7.27|^6.5.14", - "scrutinizer/ocular": "^1.1", - "symfony/finder": "^3.0|^4.0" + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan-shim": "^0.11.5", + "phpunit/phpunit": "^7.5", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" }, "suggest": { "league/commonmark-extras": "Library of useful extensions including smart punctuation" @@ -1694,12 +1691,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.19-dev" + "dev-master": "0.20-dev" } }, "autoload": { "psr-4": { - "League\\CommonMark\\": "src/" + "League\\CommonMark\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1715,13 +1712,13 @@ } ], "description": "PHP Markdown parser based on the CommonMark spec", - "homepage": "https://github.com/thephpleague/commonmark", + "homepage": "https://commonmark.thephpleague.com", "keywords": [ "commonmark", "markdown", "parser" ], - "time": "2019-03-28T13:52:31+00:00" + "time": "2019-04-11T04:37:01+00:00" }, { "name": "league/csv", @@ -2309,16 +2306,16 @@ }, { "name": "nesbot/carbon", - "version": "2.16.2", + "version": "2.16.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "720a9c36927396efeeb48a972e9d129d44b6dc28" + "reference": "373d9f0d58651af366435148c39beb702c2b7ef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/720a9c36927396efeeb48a972e9d129d44b6dc28", - "reference": "720a9c36927396efeeb48a972e9d129d44b6dc28", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/373d9f0d58651af366435148c39beb702c2b7ef4", + "reference": "373d9f0d58651af366435148c39beb702c2b7ef4", "shasum": "" }, "require": { @@ -2365,7 +2362,7 @@ "datetime", "time" ], - "time": "2019-03-29T12:23:12+00:00" + "time": "2019-04-06T17:09:23+00:00" }, { "name": "opis/closure", @@ -3308,16 +3305,16 @@ }, { "name": "symfony/console", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9" + "reference": "24206aff3efe6962593297e57ef697ebb220e384" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9", + "url": "https://api.github.com/repos/symfony/console/zipball/24206aff3efe6962593297e57ef697ebb220e384", + "reference": "24206aff3efe6962593297e57ef697ebb220e384", "shasum": "" }, "require": { @@ -3376,7 +3373,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-04-01T07:32:59+00:00" }, { "name": "symfony/contracts", @@ -3448,7 +3445,7 @@ }, { "name": "symfony/css-selector", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", @@ -3501,16 +3498,16 @@ }, { "name": "symfony/debug", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f" + "reference": "43ce8ab34c734dcc8a4af576cb86711daab964c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/de73f48977b8eaf7ce22814d66e43a1662cc864f", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f", + "url": "https://api.github.com/repos/symfony/debug/zipball/43ce8ab34c734dcc8a4af576cb86711daab964c5", + "reference": "43ce8ab34c734dcc8a4af576cb86711daab964c5", "shasum": "" }, "require": { @@ -3553,20 +3550,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-03-03T18:11:24+00:00" + "time": "2019-03-10T17:09:50+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb" + "reference": "ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544", + "reference": "ca5af306fbc37f3cf597e91bc9cfa0c7d3f33544", "shasum": "" }, "require": { @@ -3617,11 +3614,11 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-03-30T15:58:42+00:00" }, { "name": "symfony/finder", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", @@ -3670,16 +3667,16 @@ }, { "name": "symfony/http-foundation", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77" + "reference": "5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/850a667d6254ccf6c61d853407b16f21c4579c77", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1", + "reference": "5b7ab6beaa5b053b8d3c9b13367ada9b292e12e1", "shasum": "" }, "require": { @@ -3720,20 +3717,20 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-02-26T08:03:39+00:00" + "time": "2019-03-30T15:58:42+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a" + "reference": "e8b940bbeebf0f96789b5d17d9d77f8b2613960b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/895ceccaa8149f9343e6134e607c21da42d73b7a", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/e8b940bbeebf0f96789b5d17d9d77f8b2613960b", + "reference": "e8b940bbeebf0f96789b5d17d9d77f8b2613960b", "shasum": "" }, "require": { @@ -3809,7 +3806,7 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-03-03T19:38:09+00:00" + "time": "2019-04-02T19:03:51+00:00" }, { "name": "symfony/polyfill-ctype", @@ -4214,16 +4211,16 @@ }, { "name": "symfony/process", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" + "reference": "1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "url": "https://api.github.com/repos/symfony/process/zipball/1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6", + "reference": "1e6cbb41dadcaf29e0db034d6ad0d039a9df06e6", "shasum": "" }, "require": { @@ -4259,7 +4256,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-01-24T22:05:03+00:00" + "time": "2019-03-10T20:07:02+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -4328,16 +4325,16 @@ }, { "name": "symfony/routing", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42" + "reference": "319f600c1ea0f981f6bdc2f042cfc1690957c0e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42", - "reference": "ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42", + "url": "https://api.github.com/repos/symfony/routing/zipball/319f600c1ea0f981f6bdc2f042cfc1690957c0e0", + "reference": "319f600c1ea0f981f6bdc2f042cfc1690957c0e0", "shasum": "" }, "require": { @@ -4360,7 +4357,6 @@ "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", - "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" @@ -4401,20 +4397,20 @@ "uri", "url" ], - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-03-30T15:58:42+00:00" }, { "name": "symfony/translation", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f" + "reference": "e46933cc31b68f51f7fc5470fb55550407520f56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/748464177a77011f8f4cdd076773862ce4915f8f", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f", + "url": "https://api.github.com/repos/symfony/translation/zipball/e46933cc31b68f51f7fc5470fb55550407520f56", + "reference": "e46933cc31b68f51f7fc5470fb55550407520f56", "shasum": "" }, "require": { @@ -4474,11 +4470,11 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-02-27T03:31:50+00:00" + "time": "2019-04-01T14:13:08+00:00" }, { "name": "symfony/var-dumper", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", @@ -4554,16 +4550,16 @@ }, { "name": "tightenco/collect", - "version": "v5.8.8", + "version": "v5.8.11", "source": { "type": "git", "url": "https://github.com/tightenco/collect.git", - "reference": "96297c72453eaf311dd0ce448ee48168a994ba97" + "reference": "1e4120c90b3536a9ebd080d50ecaae7b75719054" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tightenco/collect/zipball/96297c72453eaf311dd0ce448ee48168a994ba97", - "reference": "96297c72453eaf311dd0ce448ee48168a994ba97", + "url": "https://api.github.com/repos/tightenco/collect/zipball/1e4120c90b3536a9ebd080d50ecaae7b75719054", + "reference": "1e4120c90b3536a9ebd080d50ecaae7b75719054", "shasum": "" }, "require": { @@ -4600,7 +4596,7 @@ "collection", "laravel" ], - "time": "2019-03-20T13:29:24+00:00" + "time": "2019-04-02T20:31:59+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -5016,16 +5012,16 @@ }, { "name": "composer/composer", - "version": "1.8.4", + "version": "1.8.5", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "bc364c2480c17941e2135cfc568fa41794392534" + "reference": "949b116f9e7d98d8d276594fed74b580d125c0e6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/bc364c2480c17941e2135cfc568fa41794392534", - "reference": "bc364c2480c17941e2135cfc568fa41794392534", + "url": "https://api.github.com/repos/composer/composer/zipball/949b116f9e7d98d8d276594fed74b580d125c0e6", + "reference": "949b116f9e7d98d8d276594fed74b580d125c0e6", "shasum": "" }, "require": { @@ -5092,7 +5088,7 @@ "dependency", "package" ], - "time": "2019-02-11T09:52:10+00:00" + "time": "2019-04-09T15:46:48+00:00" }, { "name": "composer/semver", @@ -5656,16 +5652,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", "shasum": "" }, "require": { @@ -5700,7 +5696,7 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" + "time": "2019-04-07T13:18:21+00:00" }, { "name": "phar-io/manifest", @@ -6273,16 +6269,16 @@ }, { "name": "phpunit/phpunit", - "version": "8.0.6", + "version": "8.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "925109f8bbe6dae28fbc7bb07446a53abd3b1c25" + "reference": "e7450b51b6f5d29edcd645ff72b355ab0633ca35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/925109f8bbe6dae28fbc7bb07446a53abd3b1c25", - "reference": "925109f8bbe6dae28fbc7bb07446a53abd3b1c25", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e7450b51b6f5d29edcd645ff72b355ab0633ca35", + "reference": "e7450b51b6f5d29edcd645ff72b355ab0633ca35", "shasum": "" }, "require": { @@ -6325,7 +6321,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.0-dev" + "dev-master": "8.1-dev" } }, "autoload": { @@ -6351,7 +6347,7 @@ "testing", "xunit" ], - "time": "2019-03-26T14:00:24+00:00" + "time": "2019-04-08T16:03:02+00:00" }, { "name": "roave/security-advisories", @@ -6359,12 +6355,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "0698207bf8a9bed212fdde2d8c7cdc77085660c4" + "reference": "ff41a9a96718245c7160588928e6d217cfb0393c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/0698207bf8a9bed212fdde2d8c7cdc77085660c4", - "reference": "0698207bf8a9bed212fdde2d8c7cdc77085660c4", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ff41a9a96718245c7160588928e6d217cfb0393c", + "reference": "ff41a9a96718245c7160588928e6d217cfb0393c", "shasum": "" }, "conflict": { @@ -6384,8 +6380,8 @@ "codeigniter/framework": "<=3.0.6", "composer/composer": "<=1.0.0-alpha11", "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/core": ">=2,<3.5.35", - "contao/core-bundle": ">=4,<4.4.18|>=4.5,<4.5.8", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": ">=4,<4.4.37|>=4.5,<4.7.3", "contao/listing-bundle": ">=4,<4.4.8", "contao/newsletter-bundle": ">=4,<4.1", "david-garcia/phpwhois": "<=4.3.1", @@ -6399,9 +6395,9 @@ "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.64|>=8,<8.5.13|>=8.6,<8.6.12", + "drupal/core": ">=7,<7.65|>=8,<8.5.14|>=8.6,<8.6.13", "drupal/drupal": ">=7,<7.64|>=8,<8.5.13|>=8.6,<8.6.12", - "erusev/parsedown": "<1.7", + "erusev/parsedown": "<1.7.2", "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1", @@ -6556,7 +6552,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2019-03-28T22:30:08+00:00" + "time": "2019-04-12T16:05:24+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -7222,7 +7218,7 @@ }, { "name": "symfony/filesystem", - "version": "v4.2.4", + "version": "v4.2.5", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", @@ -7272,16 +7268,16 @@ }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/1c42705be2b6c1de5904f8afacef5895cab44bf8", + "reference": "1c42705be2b6c1de5904f8afacef5895cab44bf8", "shasum": "" }, "require": { @@ -7308,7 +7304,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-04-04T09:56:43+00:00" }, { "name": "webmozart/assert", diff --git a/public/v1/js/ff/transactions/show.js b/public/v1/js/ff/transactions/show.js index 85951f650e..e1c4ec76c0 100644 --- a/public/v1/js/ff/transactions/show.js +++ b/public/v1/js/ff/transactions/show.js @@ -22,40 +22,5 @@ $(function () { "use strict"; - var transactions = new Bloodhound({ - datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), - queryTokenizer: Bloodhound.tokenizers.whitespace, - identify: function (obj) { - return obj.id; - }, - prefetch: { - url: autoCompleteUri + '?uid=' + uid, - // filter: function (list) { - // return $.map(list, function (name) { - // return {name: name.name}; - // }); - // } - }, - remote: { - url: autoCompleteUri + '?search=%QUERY&uid=' + uid, - wildcard: '%QUERY' - // filter: function (list) { - // return $.map(list, function (name) { - // return {name: name.name}; - // }); - // } - } - }); - transactions.initialize(); - var input = $("#link_other"); - input.typeahead({hint: true, highlight: true,}, {source: transactions, displayKey: 'name', autoSelect: false}); - input.bind('typeahead:select', function (ev, suggestion) { - console.log('Selection: ' + suggestion.name); - if (suggestion.name.toLowerCase() === input.val().toLowerCase()) { - // This means the exact match is found. Use toLowerCase() if you want case insensitive match. - $('input[name="link_journal_id"]').val(suggestion.id); - } else { - $('input[name="link_journal_id"]').val(0); - } - }); + }); \ No newline at end of file diff --git a/public/v1/js/lib/vue.js b/public/v1/js/lib/vue.js new file mode 100644 index 0000000000..4ef7ff1b0a --- /dev/null +++ b/public/v1/js/lib/vue.js @@ -0,0 +1,11944 @@ +/*! + * Vue.js v2.6.10 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Vue = factory()); +}(this, function () { 'use strict'; + + /* */ + + var emptyObject = Object.freeze({}); + + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef (v) { + return v === undefined || v === null + } + + function isDef (v) { + return v !== undefined && v !== null + } + + function isTrue (v) { + return v === true + } + + function isFalse (v) { + return v === false + } + + /** + * Check if value is primitive. + */ + function isPrimitive (value) { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) + } + + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + + function toRawType (value) { + return _toString.call(value).slice(8, -1) + } + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject (obj) { + return _toString.call(obj) === '[object Object]' + } + + function isRegExp (v) { + return _toString.call(v) === '[object RegExp]' + } + + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex (val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + } + + function isPromise (val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) + } + + /** + * Convert a value to a string that is actually rendered. + */ + function toString (val) { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } + + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n + } + + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap ( + str, + expectsLowerCase + ) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } + } + + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap('slot,component', true); + + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + /** + * Remove an item from an array. + */ + function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } + } + + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + /** + * Create a cached version of a pure function. + */ + function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) + } + + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) + }); + + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) + }); + + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, '-$1').toLowerCase() + }); + + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + + /* istanbul ignore next */ + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length; + return boundFn + } + + function nativeBind (fn, ctx) { + return fn.bind(ctx) + } + + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + /** + * Convert an Array-like object to a real Array. + */ + function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret + } + + /** + * Mix properties into target object. + */ + function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to + } + + /** + * Merge an Array of Objects into a single Object. + */ + function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res + } + + /* eslint-disable no-unused-vars */ + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop (a, b, c) {} + + /** + * Always return false. + */ + var no = function (a, b, c) { return false; }; + + /* eslint-enable no-unused-vars */ + + /** + * Return the same value. + */ + var identity = function (_) { return _; }; + + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') + } + + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 + } + + /** + * Ensure a function is called only once. + */ + function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + } + } + + var SSR_ATTR = 'data-server-rendered'; + + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + /* */ + + + + var config = ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: "development" !== 'production', + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain custom elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS + }); + + /* */ + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeRegExp = /a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD/; + + /** + * Check if a string starts with $ or _ + */ + function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F + } + + /** + * Define a property. + */ + function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); + } + + /** + * Parse simple path. + */ + var bailRE = new RegExp(("[^" + (unicodeRegExp.source) + ".$_\\d]")); + function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } + } + + /* */ + + // can we use __proto__? + var hasProto = '__proto__' in {}; + + // Browser environment sniffing + var inBrowser = typeof window !== 'undefined'; + var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; + var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf('msie 9.0') > 0; + var isEdge = UA && UA.indexOf('edge/') > 0; + var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); + var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); + var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + var isPhantomJS = UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + + // Firefox has a "watch" function on Object.prototype... + var nativeWatch = ({}).watch; + + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, 'passive', ({ + get: function get () { + /* istanbul ignore next */ + supportsPassive = true; + } + })); // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts); + } catch (e) {} + } + + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer + }; + + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + + /* istanbul ignore next */ + function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) + } + + var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + + var _Set; + /* istanbul ignore if */ // $flow-disable-line + if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /*@__PURE__*/(function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); + } + + /* */ + + var warn = noop; + var tip = noop; + var generateComponentTrace = (noop); // work around flow check + var formatComponentName = (noop); + + { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + var name = options.name || options._componentTag; + var file = options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { res += str; } + if (n > 1) { str += str; } + n >>= 1; + } + return res + }; + + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + currentRecursiveSequence++; + vm = vm.$parent; + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + tree.push(vm); + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); }) + .join('\n') + } else { + return ("\n\n(found in " + (formatComponentName(vm)) + ")") + } + }; + } + + /* */ + + var uid = 0; + + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ + var Dep = function Dep () { + this.id = uid++; + this.subs = []; + }; + + Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); + }; + + Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); + }; + + Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } + }; + + Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } + }; + + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + + function pushTarget (target) { + targetStack.push(target); + Dep.target = target; + } + + function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* */ + + var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + }; + + var prototypeAccessors = { child: { configurable: true } }; + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + prototypeAccessors.child.get = function () { + return this.componentInstance + }; + + Object.defineProperties( VNode.prototype, prototypeAccessors ); + + var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node + }; + + function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) + } + + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + + var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' + ]; + + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); + }); + + /* */ + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + + function toggleObserving (value) { + shouldObserve = value; + } + + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + protoAugment(value, arrayMethods); + } else { + copyAugment(value, arrayMethods, arrayKeys); + } + this.observeArray(value); + } else { + this.walk(value); + } + }; + + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i]); + } + }; + + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } + }; + + // helpers + + /** + * Augment a target Object or Array by intercepting + * the prototype chain using __proto__ + */ + function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ + } + + /** + * Augment a target Object or Array by defining + * hidden properties. + */ + /* istanbul ignore next */ + function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } + } + + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob + } + + /** + * Define a reactive property on an Object. + */ + function defineReactive$$1 ( + obj, + key, + val, + customSetter, + shallow + ) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + val = obj[key]; + } + + var childOb = !shallow && observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + if (Array.isArray(value)) { + dependArray(value); + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (customSetter) { + customSetter(); + } + // #7981: for accessor properties without setter + if (getter && !setter) { return } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = !shallow && observe(newVal); + dep.notify(); + } + }); + } + + /** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ + function set (target, key, val) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid adding reactive properties to a Vue instance or its root $data ' + + 'at runtime - declare it upfront in the data option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val + } + + /** + * Delete a property and trigger change if necessary. + */ + function del (target, key) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid deleting properties on a Vue instance or its root $data ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); + } + + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } + } + + /* */ + + /** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ + var strats = config.optionMergeStrategies; + + /** + * Options with restrictions + */ + { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; + } + + /** + * Helper that recursively merges two data objects together. + */ + function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + // in case the object is already observed... + if (key === '__ob__') { continue } + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + mergeData(toVal, fromVal); + } + } + return to + } + + /** + * Data + */ + function mergeDataOrFn ( + parentVal, + childVal, + vm + ) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } + } + + strats.data = function ( + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + warn( + 'The "data" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) + }; + + /** + * Hooks and props are merged as arrays. + */ + function mergeHook ( + parentVal, + childVal + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res + } + + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]); + } + } + return res + } + + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + + /** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ + function mergeAssets ( + parentVal, + childVal, + vm, + key + ) { + var res = Object.create(parentVal || null); + if (childVal) { + assertObjectType(key, childVal, vm); + return extend(res, childVal) + } else { + return res + } + } + + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + + /** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ + strats.watch = function ( + parentVal, + childVal, + vm, + key + ) { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret + }; + + /** + * Other object hashes. + */ + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + if (childVal) { extend(ret, childVal); } + return ret + }; + strats.provide = mergeDataOrFn; + + /** + * Default strategy. + */ + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + + /** + * Validate component names + */ + function checkComponents (options) { + for (var key in options.components) { + validateComponentName(key); + } + } + + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + (unicodeRegExp.source) + "]*$")).test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid custom element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + + /** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ + function normalizeProps (options, vm) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } else { + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + options.props = res; + } + + /** + * Normalize all injections into Object-based format + */ + function normalizeInject (options, vm) { + var inject = options.inject; + if (!inject) { return } + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + for (var i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + for (var key in inject) { + var val = inject[key]; + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val }; + } + } else { + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + + /** + * Normalize raw function directives into object format. + */ + function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + + /** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ + function mergeOptions ( + parent, + child, + vm + ) { + { + checkComponents(child); + } + + if (typeof child === 'function') { + child = child.options; + } + + normalizeProps(child, vm); + normalizeInject(child, vm); + normalizeDirectives(child); + + // Apply extends and mixins on the child options, + // but only if it is a raw options object that isn't + // the result of another mergeOptions call. + // Only merged options has the _base property. + if (!child._base) { + if (child.extends) { + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options + } + + /** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ + function resolveAsset ( + options, + type, + id, + warnMissing + ) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + + /* */ + + + + function validateProp ( + key, + propOptions, + propsData, + vm + ) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // boolean casting + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + { + assertProp(prop, key, value, vm, absent); + } + return value + } + + /** + * Get the default value of a prop. + */ + function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + + /** + * Assert whether a prop is valid. + */ + function assertProp ( + prop, + name, + value, + vm, + absent + ) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + + if (!valid) { + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: custom validator check failed for prop "' + name + '".', + vm + ); + } + } + } + + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + + function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + var t = typeof value; + valid = t === expectedType.toLowerCase(); + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + + /** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ + function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + + function isSameType (a, b) { + return getType(a) === getType(b) + } + + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // check if we need to specify expected value + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // check if we need to specify received value + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + + /* */ + + function handleError (err, vm, info) { + // Deactivate deps tracking while processing error handler to avoid possible infinite rendering. + // See: https://github.com/vuejs/vuex/issues/1505 + pushTarget(); + try { + if (vm) { + var cur = vm; + while ((cur = cur.$parent)) { + var hooks = cur.$options.errorCaptured; + if (hooks) { + for (var i = 0; i < hooks.length; i++) { + try { + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + globalHandleError(err, vm, info); + } finally { + popTarget(); + } + } + + function invokeWithErrorHandling ( + handler, + context, + args, + vm, + info + ) { + var res; + try { + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res) && !res._handled) { + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + // issue #9511 + // avoid catch triggering multiple times when nested calls + res._handled = true; + } + } catch (e) { + handleError(e, vm, info); + } + return res + } + + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // if the user intentionally throws the original error in the handler, + // do not log it twice + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + logError(err, vm, info); + } + + function logError (err, vm, info) { + { + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } + + /* */ + + var isUsingMicroTask = false; + + var callbacks = []; + var pending = false; + + function flushCallbacks () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // Here we have async deferring wrappers using microtasks. + // In 2.5 we used (macro) tasks (in combination with microtasks). + // However, it has subtle problems when state is changed right before repaint + // (e.g. #6813, out-in transitions). + // Also, using (macro) tasks in event handler would cause some weird behaviors + // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). + // So we now use microtasks everywhere, again. + // A major drawback of this tradeoff is that there are some scenarios + // where microtasks have too high a priority and fire in between supposedly + // sequential events (e.g. #4521, #6690, which have workarounds) + // or even between bubbling of the same event (#6566). + var timerFunc; + + // The nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore next, $flow-disable-line */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(flushCallbacks); + // In problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // Use MutationObserver where native Promise is not available, + // e.g. PhantomJS, iOS7, Android 4.4 + // (#6466 MutationObserver is unreliable in IE11) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // Fallback to setImmediate. + // Techinically it leverages the (macro) task queue, + // but it is still a better choice than setTimeout. + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // Fallback to setTimeout. + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + function nextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { + try { + cb.call(ctx); + } catch (e) { + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + _resolve(ctx); + } + }); + if (!pending) { + pending = true; + timerFunc(); + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } + + /* */ + + var mark; + var measure; + + { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + /* not type checking this file because flow doesn't play well with Proxy */ + + var initProxy; + + { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the data option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$data." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /* */ + + var seenObjects = new _Set(); + + /** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ + function traverse (val) { + _traverse(val, seenObjects); + seenObjects.clear(); + } + + function _traverse (val, seen) { + var i, keys; + var isA = Array.isArray(val); + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } + seen.add(depId); + } + if (isA) { + i = val.length; + while (i--) { _traverse(val[i], seen); } + } else { + keys = Object.keys(val); + i = keys.length; + while (i--) { _traverse(val[keys[i]], seen); } + } + } + + /* */ + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === '&'; + name = passive ? name.slice(1) : name; + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + + function createFnInvoker (fns, vm) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + invoker.fns = fns; + return invoker + } + + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + + /* */ + + function mergeVNodeHook (def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; + } + + /* */ + + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res + } + + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false + } + + /* */ + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children + } + + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.