From ec636c95a1335aebd125116f0234dbaacc4c2562 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 13 Aug 2017 12:30:28 +0200 Subject: [PATCH 001/668] Improve update and verify routines. --- app/Console/Commands/UpgradeDatabase.php | 440 +++++++++++------------ app/Console/Commands/VerifyDatabase.php | 35 ++ 2 files changed, 238 insertions(+), 237 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 066e4496d3..90d2434d76 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -20,7 +20,6 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\AccountType; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\LimitRepetition; -use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; @@ -71,76 +70,25 @@ class UpgradeDatabase extends Command { $this->setTransactionIdentifier(); $this->migrateRepetitions(); - $this->repairPiggyBanks(); $this->updateAccountCurrencies(); - $this->updateJournalCurrencies(); - $this->currencyInfoToTransactions(); - $this->verifyCurrencyInfo(); + $this->updateTransferCurrencies(); + $this->updateOtherCurrencies(); $this->info('Firefly III database is up to date.'); + return; + + + } /** - * Moves the currency id info to the transaction instead of the journal. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped. + * Migrate budget repetitions to new format where the end date is in the budget limit as well, + * making the limit_repetition table obsolete. */ - private function currencyInfoToTransactions() + public function migrateRepetitions(): void { - $count = 0; - $set = TransactionJournal::with('transactions')->get(); - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - if (is_null($transaction->transaction_currency_id)) { - $transaction->transaction_currency_id = $journal->transaction_currency_id; - $transaction->save(); - $count++; - } - } - - // read and use the foreign amounts when present. - if ($journal->hasMeta('foreign_amount')) { - $amount = Steam::positive($journal->getMeta('foreign_amount')); - - // update both transactions: - foreach ($journal->transactions as $transaction) { - $transaction->foreign_amount = $amount; - if (bccomp($transaction->amount, '0') === -1) { - // update with negative amount: - $transaction->foreign_amount = bcmul($amount, '-1'); - } - // set foreign currency id: - $transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id')); - $transaction->save(); - } - $journal->deleteMeta('foreign_amount'); - $journal->deleteMeta('foreign_currency_id'); - } - - } - - $this->line(sprintf('Updated currency information for %d transactions', $count)); - } - - /** - * Migrate budget repetitions to new format. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5. - */ - private function migrateRepetitions() - { - if (!Schema::hasTable('budget_limits')) { - return; - } - // get all budget limits with end_date NULL $set = BudgetLimit::whereNull('end_date')->get(); - if ($set->count() > 0) { - $this->line(sprintf('Found %d budget limit(s) to update', $set->count())); - } /** @var BudgetLimit $budgetLimit */ foreach ($set as $budgetLimit) { - // get limit repetition (should be just one): /** @var LimitRepetition $repetition */ $repetition = $budgetLimit->limitrepetitions()->first(); if (!is_null($repetition)) { @@ -150,59 +98,30 @@ class UpgradeDatabase extends Command $repetition->delete(); } } + + return; } /** - * Make sure there are only transfers linked to piggy bank events. + * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier + * to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example. * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped. + * In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5. + * + * When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would + * think. So each set gets a number (1,2,3) to keep them apart. */ - private function repairPiggyBanks() - { - // if table does not exist, return false - if (!Schema::hasTable('piggy_bank_events')) { - return; - } - - $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); - /** @var PiggyBankEvent $event */ - foreach ($set as $event) { - - if (is_null($event->transaction_journal_id)) { - continue; - } - /** @var TransactionJournal $journal */ - $journal = $event->transactionJournal()->first(); - if (is_null($journal)) { - continue; - } - - $type = $journal->transactionType->type; - if ($type !== TransactionType::TRANSFER) { - $event->transaction_journal_id = null; - $event->save(); - $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); - } - } - } - - /** - * This is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird. - */ - private function setTransactionIdentifier() + public function setTransactionIdentifier(): void { // if table does not exist, return false if (!Schema::hasTable('transaction_journals')) { return; } - - - $subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('transaction_journals.deleted_at') - ->whereNull('transactions.deleted_at') - ->groupBy(['transaction_journals.id']) - ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]); - + $subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNull('transaction_journals.deleted_at') + ->whereNull('transactions.deleted_at') + ->groupBy(['transaction_journals.id']) + ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]); $result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived')) ->mergeBindings($subQuery->getQuery()) ->where('t_count', '>', 2) @@ -210,55 +129,178 @@ class UpgradeDatabase extends Command $journalIds = array_unique($result->pluck('id')->toArray()); foreach ($journalIds as $journalId) { - $this->updateJournal(intval($journalId)); + $this->updateJournalidentifiers(intval($journalId)); } + + return; } /** - * Make sure all accounts have proper currency info. + * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon + * the account. */ - private function updateAccountCurrencies() + public function updateAccountCurrencies(): void { $accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']); - /** @var Account $account */ - foreach ($accounts as $account) { - // get users preference, fall back to system pref. - $defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; - $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); - $accountCurrency = intval($account->getMeta('currency_id')); - $openingBalance = $account->getOpeningBalance(); - $obCurrency = intval($openingBalance->transaction_currency_id); + $accounts->each( + function (Account $account) { + // get users preference, fall back to system pref. + $defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; + $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); + $accountCurrency = intval($account->getMeta('currency_id')); + $openingBalance = $account->getOpeningBalance(); + $obCurrency = intval($openingBalance->transaction_currency_id); - // both 0? set to default currency: - if ($accountCurrency === 0 && $obCurrency === 0) { - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - continue; + // both 0? set to default currency: + if ($accountCurrency === 0 && $obCurrency === 0) { + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); + + return true; + } + + // account is set to 0, opening balance is not? + if ($accountCurrency === 0 && $obCurrency > 0) { + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); + + return true; + } + + // do not match and opening balance id is not null. + if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) { + // update opening balance: + $openingBalance->transaction_currency_id = $accountCurrency; + $openingBalance->save(); + $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); + + return true; + } + + return true; } + ); - // account is set to 0, opening balance is not? - if ($accountCurrency === 0 && $obCurrency > 0) { - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - continue; + return; + } + + /** + * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for + * the accounts they are linked to. + * + * Both source and destination must match the respective currency preference of the related asset account. + * So FF3 must verify all transactions. + */ + public function updateOtherCurrencies() + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) + ->get(['transaction_journals.*']); + + $set->each( + function (TransactionJournal $journal) use ($repository) { + // get the transaction with the asset account in it: + /** @var Transaction $transaction */ + $transaction = $journal->transactions() + ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']); + /** @var Account $account */ + $account = $transaction->account; + $currency = $repository->find(intval($account->getMeta('currency_id'))); + $transactions = $journal->transactions()->get(); + $transactions->each( + function (Transaction $transaction) use ($currency) { + if (is_null($transaction->transaction_currency_id)) { + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + $this->line(sprintf('Transaction #%d is set to %s', $transaction->id, $currency->code)); + } + + // when mismatch in transaction: + if ($transaction->transaction_currency_id !== $currency->id) { + $this->line( + sprintf( + 'Transaction #%d is set to %s and foreign %s', $transaction->id, $currency->code, $transaction->transactionCurrency->code + ) + ); + $transaction->foreign_currency_id = $transaction->transaction_currency_id; + $transaction->foreign_amount = $transaction->amount; + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + } + ); + // also update the journal, of course: + $journal->transaction_currency_id = $currency->id; + $journal->save(); } + ); + return; + } - // do not match: - if ($accountCurrency !== $obCurrency) { - // update opening balance: - $openingBalance->transaction_currency_id = $accountCurrency; - $openingBalance->save(); - $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); - continue; + /** + * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. + * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they + * like it or not. Previous routines MUST have set the currency setting for both accounts for this to work. + * + * Both source and destination must match the respective currency preference. So FF3 must verify ALL + * transactions. + */ + public function updateTransferCurrencies() + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $set = TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->where('transaction_types.type', TransactionType::TRANSFER) + ->get(['transaction_journals.*']); + + $set->each( + function (TransactionJournal $transfer) use ($repository) { + /** @var Transaction $transaction */ + $transaction = $transfer->transactions()->where('amount', '<', 0)->first(); + $this->updateTransactionCurrency($transaction); + $this->updateJournalCurrency($transaction); + + + /** @var Transaction $transaction */ + $transaction = $transfer->transactions()->where('amount', '>', 0)->first(); + $this->updateTransactionCurrency($transaction); } + ); + } - // opening balance 0, account not zero? just continue: - // both are equal, just continue: + /** + * This method makes sure that the transaction journal uses the currency given in the transaction. + * + * @param Transaction $transaction + */ + private function updateJournalCurrency(Transaction $transaction): void + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $currency = $repository->find(intval($transaction->account->getMeta('currency_id'))); + $journal = $transaction->transactionJournal; + + if ($currency->id !== $journal->transaction_currency_id) { + $this->line( + sprintf( + 'Transfer #%d ("%s") has been updated to use %s instead of %s.', $journal->id, $journal->description, $currency->code, + $journal->transactionCurrency->code + ) + ); + $journal->transaction_currency_id = $currency->id; + $journal->save(); } + return; } /** @@ -267,7 +309,7 @@ class UpgradeDatabase extends Command * * @param int $journalId */ - private function updateJournal(int $journalId) + private function updateJournalidentifiers(int $journalId): void { $identifier = 0; $processed = []; @@ -295,121 +337,45 @@ class UpgradeDatabase extends Command if (!is_null($opposing)) { // give both a new identifier: $transaction->identifier = $identifier; + $opposing->identifier = $identifier; $transaction->save(); - $opposing->identifier = $identifier; $opposing->save(); $processed[] = $transaction->id; $processed[] = $opposing->id; } $identifier++; } + + return; } /** - * Makes sure that withdrawals, deposits and transfers have - * a currency setting matching their respective accounts - */ - private function updateJournalCurrencies() - { - $types = [ - TransactionType::WITHDRAWAL => '<', - TransactionType::DEPOSIT => '>', - ]; - $repository = app(CurrencyRepositoryInterface::class); - $notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.'; - $transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.'; - $driver = DB::connection()->getDriverName(); - $pgsql = ['pgsql', 'postgresql']; - - foreach ($types as $type => $operator) { - $query = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin( - 'transactions', function (JoinClause $join) use ($operator) { - $join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0'); - } - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') - ->where('transaction_types.type', $type) - ->where('account_meta.name', 'currency_id'); - if (in_array($driver, $pgsql)) { - $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)')); - } - if (!in_array($driver, $pgsql)) { - $query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data')); - } - - $set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']); - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $expectedCurrency = $repository->find(intval($journal->expected_currency_id)); - $line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code); - - $journal->setMeta('foreign_amount', $journal->transaction_amount); - $journal->setMeta('foreign_currency_id', $journal->transaction_currency_id); - $journal->transaction_currency_id = $expectedCurrency->id; - $journal->save(); - $this->line($line); - } - } - /* - * For transfers it's slightly different. Both source and destination - * must match the respective currency preference. So we must verify ALL - * transactions. - */ - $set = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_types.type', TransactionType::TRANSFER) - ->get(['transaction_journals.*']); - /** @var TransactionJournal $journal */ - foreach ($set as $journal) { - $updated = false; - /** @var Transaction $sourceTransaction */ - $sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first(); - $sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id'))); - - if ($sourceCurrency->id !== $journal->transaction_currency_id) { - $updated = true; - $journal->transaction_currency_id = $sourceCurrency->id; - $journal->save(); - } - - // destination - $destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first(); - $destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id'))); - - if ($destinationCurrency->id !== $journal->transaction_currency_id) { - $updated = true; - $journal->deleteMeta('foreign_amount'); - $journal->deleteMeta('foreign_currency_id'); - $journal->setMeta('foreign_amount', $destinationTransaction->amount); - $journal->setMeta('foreign_currency_id', $destinationCurrency->id); - } - if ($updated) { - $line = sprintf($transfer, $journal->id); - $this->line($line); - } - } - } - - /** + * This method makes sure that the tranaction uses the same currency as the main account does. + * If not, the currency is updated to include a reference to its original currency as the "foreign" currency. * + * @param Transaction $transaction */ - private function verifyCurrencyInfo() + private function updateTransactionCurrency(Transaction $transaction): void { - $count = 0; - $transactions = Transaction::get(); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $currencyId = intval($transaction->transaction_currency_id); - $foreignId = intval($transaction->foreign_currency_id); - if ($currencyId === $foreignId) { - $transaction->foreign_currency_id = null; - $transaction->foreign_amount = null; - $transaction->save(); - $count++; - } + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $currency = $repository->find(intval($transaction->account->getMeta('currency_id'))); + + if (is_null($transaction->transaction_currency_id)) { + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + $this->line(sprintf('Transaction #%d is set to %s', $transaction->id, $currency->code)); } - $this->line(sprintf('Updated currency information for %d transactions', $count)); + + // when mismatch in transaction: + if ($transaction->transaction_currency_id !== $currency->id) { + $this->line(sprintf('Transaction #%d is set to %s and foreign %s', $transaction->id, $currency->code, $transaction->transactionCurrency->code)); + $transaction->foreign_currency_id = $transaction->transaction_currency_id; + $transaction->foreign_amount = $transaction->amount; + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + + return; } } diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index 6e128e7f53..ffc8f1b667 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -17,6 +17,7 @@ use Crypt; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; +use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -94,6 +95,40 @@ class VerifyDatabase extends Command // report on journals with the wrong types of accounts. $this->reportIncorrectJournals(); + // report (and fix) piggy banks + $this->repairPiggyBanks(); + + } + + /** + * Make sure there are only transfers linked to piggy bank events. + */ + private function repairPiggyBanks(): void + { + $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); + $set->each( + function (PiggyBankEvent $event) { + if (is_null($event->transaction_journal_id)) { + return true; + } + /** @var TransactionJournal $journal */ + $journal = $event->transactionJournal()->first(); + if (is_null($journal)) { + return true; + } + + $type = $journal->transactionType->type; + if ($type !== TransactionType::TRANSFER) { + $event->transaction_journal_id = null; + $event->save(); + $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); + } + + return true; + } + ); + + return; } /** From f9c85d4d81863ebdc879a01fe42e36a587904345 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 13 Aug 2017 12:37:09 +0200 Subject: [PATCH 002/668] Catch the error in #760 --- app/Import/Object/ImportJournal.php | 4 ++++ app/Import/Storage/ImportStorage.php | 4 +++- app/Import/Storage/ImportSupport.php | 2 +- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php index a72f574f28..48b7d7df60 100644 --- a/app/Import/Object/ImportJournal.php +++ b/app/Import/Object/ImportJournal.php @@ -85,6 +85,7 @@ class ImportJournal /** * @return string + * @throws FireflyException */ public function getAmount(): string { @@ -102,6 +103,9 @@ class ImportJournal } } } + if(bccomp($this->amount,'0') === 0) { + throw new FireflyException('Amount is zero.'); + } return $this->amount; } diff --git a/app/Import/Storage/ImportStorage.php b/app/Import/Storage/ImportStorage.php index 50980ad2a9..598380a06b 100644 --- a/app/Import/Storage/ImportStorage.php +++ b/app/Import/Storage/ImportStorage.php @@ -11,6 +11,8 @@ declare(strict_types=1); namespace FireflyIII\Import\Storage; +use ErrorException; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Import\Object\ImportJournal; use FireflyIII\Models\ImportJob; @@ -95,7 +97,7 @@ class ImportStorage function (ImportJournal $importJournal, int $index) { try { $this->storeImportJournal($index, $importJournal); - } catch (FireflyException $e) { + } catch (FireflyException | ErrorException | Exception $e) { $this->errors->push($e->getMessage()); Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage())); } diff --git a/app/Import/Storage/ImportSupport.php b/app/Import/Storage/ImportSupport.php index 38b444b98e..aa7b1dbf99 100644 --- a/app/Import/Storage/ImportSupport.php +++ b/app/Import/Storage/ImportSupport.php @@ -214,7 +214,7 @@ trait ImportSupport */ private function getTransactionType(string $amount, Account $account): string { - $transactionType = ''; + $transactionType = TransactionType::WITHDRAWAL; // amount is negative, it's a withdrawal, opposing is an expense: if (bccomp($amount, '0') === -1) { $transactionType = TransactionType::WITHDRAWAL; From 1633994fbdf8c4e2609ba1d9e50f616e44d2b63d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 13 Aug 2017 15:30:39 +0200 Subject: [PATCH 003/668] Slight change in amount handler. #760 --- app/Import/Object/ImportJournal.php | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php index 48b7d7df60..21bd535b8f 100644 --- a/app/Import/Object/ImportJournal.php +++ b/app/Import/Object/ImportJournal.php @@ -37,6 +37,8 @@ class ImportJournal public $budget; /** @var ImportCategory */ public $category; + /** @var ImportCurrency */ + public $currency; /** @var string */ public $description = ''; /** @var string */ @@ -51,8 +53,8 @@ class ImportJournal public $tags = []; /** @var string */ private $amount; - /** @var ImportCurrency */ - public $currency; + /** @var string */ + private $convertedAmount = null; /** @var string */ private $date = ''; /** @var string */ @@ -89,25 +91,25 @@ class ImportJournal */ public function getAmount(): string { - if (is_null($this->amount)) { + if (is_null($this->convertedAmount)) { /** @var ConverterInterface $amountConverter */ - $amountConverter = app(Amount::class); - $this->amount = $amountConverter->convert($this->amount); + $amountConverter = app(Amount::class); + $this->convertedAmount = $amountConverter->convert($this->amount); // modify foreach ($this->modifiers as $modifier) { $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role']))); /** @var ConverterInterface $converter */ $converter = app($class); if ($converter->convert($modifier['value']) === -1) { - $this->amount = Steam::negative($this->amount); + $this->convertedAmount = Steam::negative($this->convertedAmount); } } } - if(bccomp($this->amount,'0') === 0) { + if (bccomp($this->convertedAmount, '0') === 0) { throw new FireflyException('Amount is zero.'); } - return $this->amount; + return $this->convertedAmount; } /** From 5a9a6a4680d0e14741df55c9b35e6ffe1debd75f Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 16:10:14 +0200 Subject: [PATCH 004/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index ac03875f19..68fced7524 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -14,10 +14,10 @@ declare(strict_types=1); return [ // initial config - 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', - 'initial_text' => 'To be able to import your file correctly, please validate the options below.', - 'initial_box' => 'Basic CSV import setup', - 'initial_box_title' => 'Basic CSV import setup options', + 'initial_title' => 'Importer la configuration (1/3) - Configuration de l\'importation CSV de base', + 'initial_text' => 'Pour pouvoir importer votre fichier correctement, veuillez validez les options ci-dessous.', + 'initial_box' => 'Options d’importation CSV basique', + 'initial_box_title' => 'Options d’importation CSV basique', 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', From c04eb6dc2a1034fe2761c5d04a1fd3b89855d65b Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 16:20:06 +0200 Subject: [PATCH 005/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 68fced7524..f1d3890f4c 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -14,11 +14,11 @@ declare(strict_types=1); return [ // initial config - 'initial_title' => 'Importer la configuration (1/3) - Configuration de l\'importation CSV de base', + 'initial_title' => 'Importer la configuration (1/3) - Configuration de l\'importation CSV basique', 'initial_text' => 'Pour pouvoir importer votre fichier correctement, veuillez validez les options ci-dessous.', 'initial_box' => 'Options d’importation CSV basique', 'initial_box_title' => 'Options d’importation CSV basique', - 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes.', 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', From 9a29747bbfb4d974f992f7ee34f6199efcdd4e85 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 16:50:08 +0200 Subject: [PATCH 006/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index f1d3890f4c..2d79cfbbab 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -22,13 +22,13 @@ return [ 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', - 'initial_submit' => 'Continue with step 2/3', + 'initial_submit' => 'Passez à l’étape 2/3', // roles config - 'roles_title' => 'Import setup (2/3) - Define each column\'s role', + 'roles_title' => 'Importer la configuration (1/3) - Définir le rôle de chaque colonne', 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', - 'roles_table' => 'Table', - 'roles_column_name' => 'Name of column', + 'roles_table' => 'Tableau', + 'roles_column_name' => 'Nom de colonne', 'roles_column_example' => 'Column example data', 'roles_column_role' => 'Column data meaning', 'roles_do_map_value' => 'Map these values', From fe457d149c2a65a2fbf3fe86a8fa3a0a2d67962e Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 17:00:07 +0200 Subject: [PATCH 007/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 2d79cfbbab..4828e6ae1b 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -26,24 +26,24 @@ return [ // roles config 'roles_title' => 'Importer la configuration (1/3) - Définir le rôle de chaque colonne', - 'roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'roles_text' => 'Chaque colonne de votre fichier CSV contient certaines données. Veuillez indiquer quel type de données, l’importateur doit attendre. L’option de « mapper » les données signifie que vous allez lier chaque entrée trouvée dans la colonne à une valeur dans votre base de données. Souvent une colonne est la colonne contenant l’IBAN du compte opposé. Qui peut être facilement adapté aux IBAN déjà présents dans votre base de données.', 'roles_table' => 'Tableau', 'roles_column_name' => 'Nom de colonne', 'roles_column_example' => 'Column example data', 'roles_column_role' => 'Column data meaning', - 'roles_do_map_value' => 'Map these values', - 'roles_column' => 'Column', - 'roles_no_example_data' => 'No example data available', - 'roles_submit' => 'Continue with step 3/3', + 'roles_do_map_value' => 'Mapper ces valeurs', + 'roles_column' => 'Colonne', + 'roles_no_example_data' => 'Pas de données disponibles', + 'roles_submit' => 'Passez à l’étape 3/3', 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', // map data 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', 'map_text' => 'Dans les tableaux suivants, la valeur gauche vous montre des informations trouvées dans votre fichier CSV téléchargé. C’est votre rôle de mapper cette valeur, si possible, une valeur déjà présente dans votre base de données. Firefly s’en tiendra à ce mappage. Si il n’y a pas de valeur correspondante, ou vous ne souhaitez pas la valeur spécifique de la carte, ne sélectionnez rien.', - 'map_field_value' => 'Field value', - 'map_field_mapped_to' => 'Mapped to', - 'map_do_not_map' => '(do not map)', - 'map_submit' => 'Start the import', + 'map_field_value' => 'Valeur du champ', + 'map_field_mapped_to' => 'Mappé à', + 'map_do_not_map' => '(ne pas mapper)', + 'map_submit' => 'Démarrer l\'importation', // map things. 'column__ignore' => '(ignorer cette colonne)', From 4a1a70fa46b1e4b893efdd98b8a48c082cc58f68 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 17:41:49 +0200 Subject: [PATCH 008/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 4828e6ae1b..a7191b341c 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -29,7 +29,7 @@ return [ 'roles_text' => 'Chaque colonne de votre fichier CSV contient certaines données. Veuillez indiquer quel type de données, l’importateur doit attendre. L’option de « mapper » les données signifie que vous allez lier chaque entrée trouvée dans la colonne à une valeur dans votre base de données. Souvent une colonne est la colonne contenant l’IBAN du compte opposé. Qui peut être facilement adapté aux IBAN déjà présents dans votre base de données.', 'roles_table' => 'Tableau', 'roles_column_name' => 'Nom de colonne', - 'roles_column_example' => 'Column example data', + 'roles_column_example' => 'Données d’exemple de colonne', 'roles_column_role' => 'Column data meaning', 'roles_do_map_value' => 'Mapper ces valeurs', 'roles_column' => 'Colonne', From 84b5c3c789cb208489959048b7510a08e3a7e19e Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 17:50:12 +0200 Subject: [PATCH 009/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index a7191b341c..701f1c83fc 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -30,12 +30,12 @@ return [ 'roles_table' => 'Tableau', 'roles_column_name' => 'Nom de colonne', 'roles_column_example' => 'Données d’exemple de colonne', - 'roles_column_role' => 'Column data meaning', + 'roles_column_role' => 'Signification des données de colonne', 'roles_do_map_value' => 'Mapper ces valeurs', 'roles_column' => 'Colonne', 'roles_no_example_data' => 'Pas de données disponibles', 'roles_submit' => 'Passez à l’étape 3/3', - 'roles_warning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'roles_warning' => 'La moindre des choses, c\'est de marquer une colonne comme colonne-montant. Il est conseillé de sélectionner également une colonne pour la description, la date et le compte opposé.', // map data 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', From 3182256580bd03de24a7e87adee45348fd70e162 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:00:11 +0200 Subject: [PATCH 010/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index 701f1c83fc..afd0dbf930 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -19,9 +19,9 @@ return [ 'initial_box' => 'Options d’importation CSV basique', 'initial_box_title' => 'Options d’importation CSV basique', 'initial_header_help' => 'Cochez cette case si la première ligne de votre fichier CSV contient les entêtes des colonnes.', - 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', - 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', + 'initial_date_help' => 'Le format de la date et de l’heure dans votre fichier CSV. Utiliser le format comme indiqué sur cette page. La valeur par défaut va analyser les dates ressemblant à ceci : :dateExample.', + 'initial_delimiter_help' => 'Choisissez le délimiteur de champ qui est utilisé dans votre fichier d’entrée. Si vous n’êtes pas certain, la virgule est l’option la plus sûre.', + 'initial_import_account_help' => 'Si votre fichier CSV ne contient AUCUNE information concernant vos compte(s) actif, utilisez cette liste déroulante pour choisir à quel compte les opérations contenues dans le CSV font référence.', 'initial_submit' => 'Passez à l’étape 2/3', // roles config @@ -38,7 +38,7 @@ return [ 'roles_warning' => 'La moindre des choses, c\'est de marquer une colonne comme colonne-montant. Il est conseillé de sélectionner également une colonne pour la description, la date et le compte opposé.', // map data - 'map_title' => 'Import setup (3/3) - Connect import data to Firefly III data', + 'map_title' => 'Importer la configuration (3/3) - Connecter l\'importation des données aux données de Firefly III', 'map_text' => 'Dans les tableaux suivants, la valeur gauche vous montre des informations trouvées dans votre fichier CSV téléchargé. C’est votre rôle de mapper cette valeur, si possible, une valeur déjà présente dans votre base de données. Firefly s’en tiendra à ce mappage. Si il n’y a pas de valeur correspondante, ou vous ne souhaitez pas la valeur spécifique de la carte, ne sélectionnez rien.', 'map_field_value' => 'Valeur du champ', 'map_field_mapped_to' => 'Mappé à', @@ -73,8 +73,8 @@ return [ 'column_opposing-name' => 'Compte destination (nom)', 'column_rabo-debet-credit' => 'Indicateur spécifique débit/crédit à Rabobank', 'column_ing-debet-credit' => 'Indicateur spécifique débit/crédit à ING', - 'column_sepa-ct-id' => 'SEPA Credit Transfer end-to-end ID', - 'column_sepa-ct-op' => 'SEPA Credit Transfer opposing account', + 'column_sepa-ct-id' => 'SEPA Transfert Crédit ID de bout en bout', + 'column_sepa-ct-op' => 'SEPA Transfert Crédit compte opposé', 'column_sepa-db' => 'SEPA débit immédiat', 'column_tags-comma' => 'Tags (séparé par des virgules)', 'column_tags-space' => 'Tags(séparé par des espaces)', From 5d3df4579e2cd0f74504103714a0e6ef91c4ac7a Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:00:13 +0200 Subject: [PATCH 011/668] New translations list.php (French) --- resources/lang/fr_FR/list.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index 003c518d2b..ce34a53af3 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -36,7 +36,7 @@ return [ 'split_number' => 'Segmenter en', 'destination' => 'Destination', 'source' => 'Source', - 'next_expected_match' => 'Next expected match', + 'next_expected_match' => 'Prochaine association attendue', 'automatch' => 'Correspondance automatique ?', 'repeat_freq' => 'Répétitions', 'description' => 'Description', @@ -70,22 +70,22 @@ return [ 'is_blocked' => 'Est bloqué', 'is_admin' => 'Est admin', 'has_two_factor' => 'A 2FA', - 'confirmed_from' => 'Confirmed from', + 'confirmed_from' => 'Confirmé à partir de', 'registered_from' => 'Inscrit depuis le', 'blocked_code' => 'Code de blocage', 'domain' => 'Domaine', - 'registration_attempts' => 'Registration attempts', + 'registration_attempts' => 'Tentatives d\'inscription', 'source_account' => 'Compte d\'origine', 'destination_account' => 'Compte destinataire', 'accounts_count' => 'Nombre de comptes', 'journals_count' => 'Nombre d\'opérations', 'attachments_count' => 'Nombre de pièces jointes', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', + 'bills_count' => 'Nombre de factures', + 'categories_count' => 'Nombre de catégories', + 'export_jobs_count' => 'Nombre de travaux exportés', + 'import_jobs_count' => 'Nombre de travaux importés', + 'budget_count' => 'Nombre de budgets', 'rule_and_groups_count' => 'Nombre de règles et de groupes de règles', 'tags_count' => 'Number of tags', ]; \ No newline at end of file From a785fd27ecfa749ae6dceb9bc99dda3d5873d144 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:10:07 +0200 Subject: [PATCH 012/668] New translations validation.php (French) --- resources/lang/fr_FR/validation.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index 7328b1530a..6eacebc9f8 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -90,5 +90,5 @@ return [ 'in_array' => 'Le champ :attribute n\'existe pas dans :other.', 'present' => 'Le champs :attribute doit être rempli.', 'amount_zero' => 'Le montant total ne peut pas être zéro', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://goo.gl/NCh2tN', + 'secure_password' => 'Ce n’est pas un mot de passe sécurisé. S’il vous plaît essayer de nouveau. Pour plus d’informations, visitez https://goo.gl/NCh2tN', ]; \ No newline at end of file From 2410c767f8dbabb77cc78884213bf8c795c0c697 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:10:09 +0200 Subject: [PATCH 013/668] New translations form.php (French) --- resources/lang/fr_FR/form.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index 8451d35d30..f583f16d16 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -34,7 +34,7 @@ return [ 'journal_source_account_name' => 'Compte de recettes (source)', 'journal_source_account_id' => 'Compte d’actif (source)', 'BIC' => 'Code BIC', - 'verify_password' => 'Verify password security', + 'verify_password' => 'Vérifiez la sécurité du mot de passe', 'account_from_id' => 'Compte d\'origine', 'account_to_id' => 'Compte de destination', 'source_account' => 'Compte d\'origine', @@ -57,36 +57,36 @@ return [ 'targetamount' => 'Montant cible', 'accountRole' => 'Rôle du compte', 'openingBalanceDate' => 'Date du solde initial', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'ccType' => 'Plan de paiement de carte de crédit', + 'ccMonthlyPaymentDate' => 'Date de paiement mensuelle de carte de crédit', 'piggy_bank_id' => 'Tirelire', - 'returnHere' => 'Return here', + 'returnHere' => 'Retourner ici', 'returnHereExplanation' => 'Après enregistrement, revenir ici pour en créer un nouveau.', 'returnHereUpdateExplanation' => 'Après mise à jour, revenir ici.', 'description' => 'Description', 'expense_account' => 'Compte de dépenses', 'revenue_account' => 'Compte de recettes', - 'decimal_places' => 'Decimal places', + 'decimal_places' => 'Chiffres après la virgule', 'exchange_rate_instruction' => 'Devises étrangères', - 'exchanged_amount' => 'Exchanged amount', + 'exchanged_amount' => 'Montant échangé', 'source_amount' => 'Montant (source)', 'destination_amount' => 'Montant (destination)', - 'native_amount' => 'Native amount', + 'native_amount' => 'Montant natif', - 'revenue_account_source' => 'Revenue account (source)', - 'source_account_asset' => 'Source account (asset account)', - 'destination_account_expense' => 'Destination account (expense account)', - 'destination_account_asset' => 'Destination account (asset account)', - 'source_account_revenue' => 'Source account (revenue account)', + 'revenue_account_source' => 'Compte de recettes (source)', + 'source_account_asset' => 'Compte source (compte d\'actif)', + 'destination_account_expense' => 'Compte de destination (compte de dépenses)', + 'destination_account_asset' => 'Compte de destination (compte d’actif)', + 'source_account_revenue' => 'Compte source (compte recettes)', 'type' => 'Type', - 'convert_Withdrawal' => 'Convert withdrawal', - 'convert_Deposit' => 'Convert deposit', - 'convert_Transfer' => 'Convert transfer', + 'convert_Withdrawal' => 'Convertir le retrait', + 'convert_Deposit' => 'Convertir le dépôt', + 'convert_Transfer' => 'Convertir le transfert', 'amount' => 'Montant', 'date' => 'Date', - 'interest_date' => 'Interest date', + 'interest_date' => 'Date de l’intérêt', 'book_date' => 'Date de réservation', 'process_date' => 'Date de traitement', 'category' => 'Catégorie', @@ -102,7 +102,7 @@ return [ 'accountNumber' => 'N° de compte', 'has_headers' => 'Entêtes ', 'date_format' => 'Format de la date', - 'specifix' => 'Bank- or file specific fixes', + 'specifix' => 'Banque - ou déposer des corrections spécifiques', 'attachments[]' => 'Pièces jointes', 'store_new_withdrawal' => 'Enregistrer un nouveau retrait', 'store_new_deposit' => 'Enregistrer un nouveau dépôt', From fe24c8971f54a2ad5a63f705b1437e43e0f11349 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:10:10 +0200 Subject: [PATCH 014/668] New translations breadcrumbs.php (French) --- resources/lang/fr_FR/breadcrumbs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/breadcrumbs.php b/resources/lang/fr_FR/breadcrumbs.php index ef1952431c..48763edce1 100644 --- a/resources/lang/fr_FR/breadcrumbs.php +++ b/resources/lang/fr_FR/breadcrumbs.php @@ -26,7 +26,7 @@ return [ 'edit_bill' => 'Editer la facture : ":name"', 'delete_bill' => 'Supprimer la facture ":name"', 'reports' => 'Rapport', - 'search_result' => 'Search results for ":query"', + 'search_result' => 'Résultats de recherche pour ":query"', 'withdrawal_list' => 'Dépenses', 'deposit_list' => 'Revenu, salaire et versements', 'transfer_list' => 'Virements', From 5e423a8edef043f36327beac6c2012cb3f31be78 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 18:10:11 +0200 Subject: [PATCH 015/668] New translations list.php (French) --- resources/lang/fr_FR/list.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index ce34a53af3..2acf2c75d3 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -87,5 +87,5 @@ return [ 'import_jobs_count' => 'Nombre de travaux importés', 'budget_count' => 'Nombre de budgets', 'rule_and_groups_count' => 'Nombre de règles et de groupes de règles', - 'tags_count' => 'Number of tags', + 'tags_count' => 'Nombre d’étiquettes', ]; \ No newline at end of file From 244dcc04653caa727cc2ef50b3e290ae47aa50d4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 19:10:07 +0200 Subject: [PATCH 016/668] New translations demo.php (French) --- resources/lang/fr_FR/demo.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/demo.php b/resources/lang/fr_FR/demo.php index 5ef3b853d3..8b376c17a2 100644 --- a/resources/lang/fr_FR/demo.php +++ b/resources/lang/fr_FR/demo.php @@ -13,7 +13,7 @@ return [ 'no_demo_text' => 'Désolé, il n’y a aucun texte supplémentaire de démonstration ou d\'explication pour cette page.', 'see_help_icon' => 'Cependant, l\'icône située dans le coin supérieur droit peut vous en dire plus.', 'index' => 'Bienvenue chez Firefly III ! Sur cette page, vous obtenez un aperçu rapide de vos finances. Pour plus d’informations, consultez comptes → Comptes d’actif et, bien sûr, les pages des Budgets et des rapports. Ou juste jetez un coup d’œil et voyez où vous vous retrouvez.', - 'accounts-index' => 'Asset accounts are your personal bank accounts. Expense accounts are the accounts you spend money at, such as stores and friends. Revenue accounts are accounts you receive money from, such as your job, the government or other sources of income. On this page you can edit or remove them.', + 'accounts-index' => 'Les comptes d’actifs sont vos comptes bancaires personnels. Les comptes de dépenses sont des comptes où vous dépensez de l’argent, comme les magasins et les amis. Les comptes de recettes sont des comptes où vous recevez de l’argent, comme votre travail, le gouvernement ou d’autres sources de revenu. Sur cette page, vous pouvez les modifier ou les supprimer.', 'budgets-index' => 'Cette page vous présente un aperçu de vos budgets. La barre du haut affiche le montant disponible à budgétiser. Cela peut être personnalisé pour toute période en cliquant sur le montant sur la droite. Le montant que vous avez réellement dépensé s’affiche dans la barre ci-dessous. Visualisez ainsi les dépenses budgétisées et votre prévisionnel.', 'reports-index-start' => 'Firefly III prend en charge quatre types de rapports. Apprenez-en plus à leur sujet en cliquant sur l\'icône située dans le coin supérieur droit.', 'reports-index-examples' => 'N’oubliez pas de consultez ces exemples : un aperçu financier mensuel, une vue d’ensemble financière annuelle ainsi qu’une présentation du budget.', From 4219edc089818dfee83d76c0a07b712c6fb32da3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 23:40:07 +0200 Subject: [PATCH 017/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 923d9f639d..a4c53bdb81 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -70,7 +70,7 @@ return [ 'two_factor_lost_fix_owner' => 'Dans le cas contraire, contactez le propriétaire du site par courriel : site_owner et demandez leur de réinitialiser votre authentification à deux facteurs.', 'warning_much_data' => ':days de données peuvent prendre un certain temps à charger.', 'registered' => 'Vous avez été enregistré avec succès !', - 'tagbalancingAct' => 'Balancing act', + 'tagbalancingAct' => 'Acte d\'équilibre', 'tagadvancePayment' => 'Acompte', 'tagnothing' => '', 'Default asset account' => 'Compte d’actif par défaut', @@ -82,29 +82,29 @@ return [ 'user_id_is' => 'Votre identifiant d’utilisateur est :user', 'field_supports_markdown' => 'Ce champ prend en charge la syntaxe Markdown.', 'need_more_help' => 'Si vous désirez plus de renseignements sur l\'utilisation de Firefly III, merci d\'ouvrir un ticket sur Github.', - 'reenable_intro_text' => 'You can also reenable the introduction guidance.', - 'intro_boxes_after_refresh' => 'The introduction boxes will reappear when you refresh the page.', + 'reenable_intro_text' => 'Vous pouvez également réactiver le guide d\'introduction.', + 'intro_boxes_after_refresh' => 'Les boîtes d\'introduction réapparaîtront lorsque vous actualiserez la page.', 'nothing_to_display' => 'Il n’y a aucune transaction à afficher', 'show_all_no_filter' => 'Montrer toutes les transactions sans les classer par date.', 'expenses_by_category' => 'Dépenses par catégorie', 'expenses_by_budget' => 'Dépenses par budget', 'income_by_category' => 'Revenu par catégorie', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', + 'expenses_by_asset_account' => 'Dépenses par compte d’actif', + 'expenses_by_expense_account' => 'Dépenses par compte de dépenses', 'cannot_redirect_to_account' => 'Firefly III n\'est pas en mesure de vous rediriger vers la bonne page. Veuillez nous en excuser.', 'sum_of_expenses' => 'Montant des dépenses', 'sum_of_income' => 'Montant des revenus', 'total_sum' => 'Montant total ', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', + 'spent_in_specific_budget' => 'Dépensé dans le budget ":budget"', + 'sum_of_expenses_in_budget' => 'Total dépensé dans le budget ":budget"', + 'left_in_budget_limit' => 'Reste à dépenser selon budget', 'cannot_change_demo' => 'Vous ne pouvez pas modifier le mot de passe du compte de démonstration.', 'cannot_delete_demo' => 'Vous ne pouvez pas supprimer le compte de démonstration.', 'cannot_reset_demo_user' => 'Vous ne pouvez pas réinitialiser le mot de passe du compte démonstration', 'per_period' => 'Par période', 'all_periods' => 'Toutes les périodes', 'current_period' => 'Période en cours', - 'show_the_current_period_and_overview' => 'Show the current period and overview', + 'show_the_current_period_and_overview' => 'Afficher l’exercice en cours et sa vue d’ensemble', 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', From e84b37fc66f11a7a1c765cf1d20521b933c46943 Mon Sep 17 00:00:00 2001 From: James Cole Date: Mon, 14 Aug 2017 23:50:14 +0200 Subject: [PATCH 018/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index a4c53bdb81..e6caa63b6f 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -105,12 +105,12 @@ return [ 'all_periods' => 'Toutes les périodes', 'current_period' => 'Période en cours', 'show_the_current_period_and_overview' => 'Afficher l’exercice en cours et sa vue d’ensemble', - 'pref_languages_locale' => 'For a language other than English to work properly, your operating system must be equipped with the correct locale-information. If these are not present, currency data, dates and amounts may be formatted wrong.', - 'budget_in_period' => 'All transactions for budget ":name" between :start and :end', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" between :start and :end', - 'chart_category_in_period' => 'Chart for all transactions for category ":name" between :start and :end', - 'chart_category_all' => 'Chart for all transactions for category ":name"', + 'pref_languages_locale' => 'Pour une langue autre que l’anglais pour fonctionner correctement, votre système d’exploitation doit être équipé avec les paramètres régionaux correctes. Si ils ne sont pas présents, les données de devises, les dates et les montants peuvent être mal formatés.', + 'budget_in_period' => 'Toutes les transactions pour le budget ":name" entre :start et :end', + 'chart_budget_in_period' => 'Graphique pour toutes les transactions pour le budget ":name" entre :start et :end', + 'chart_account_in_period' => 'Graphique pour toutes les transactions pour le compte ":name" entre :start et :end', + 'chart_category_in_period' => 'Graphique pour toutes les transactions pour la catégorie ":name" entre :start et :end', + 'chart_category_all' => 'Graphique pour toutes les transactions pour la catégorie ":name"', 'budget_in_period_breadcrumb' => 'Entre :start et :end', 'clone_withdrawal' => 'Cloner ce retrait', 'clone_deposit' => 'Cloner ce dépôt', From 7974319c7380b491a272e793884144cf40f97b49 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 00:10:08 +0200 Subject: [PATCH 019/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index e6caa63b6f..61009dbb38 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -139,8 +139,8 @@ return [ 'all_journals_for_tag' => 'Toutes les transactions pour le tag ":tag"', 'title_transfer_between' => 'Tous les transferts entre :start et :end', 'all_journals_for_category' => 'Toutes les transactions pour la catégorie :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for budget :name', + 'all_journals_for_budget' => 'Toutes les transactions pour le budget :name', + 'chart_all_journals_for_budget' => 'Graphique pour toutes les transactions pour le budget :name', 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', From f1081e058c44ec69d1112bdffc71904b9446754a Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 00:20:07 +0200 Subject: [PATCH 020/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 61009dbb38..cca0521221 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -141,24 +141,24 @@ return [ 'all_journals_for_category' => 'Toutes les transactions pour la catégorie :name', 'all_journals_for_budget' => 'Toutes les transactions pour le budget :name', 'chart_all_journals_for_budget' => 'Graphique pour toutes les transactions pour le budget :name', - 'journals_in_period_for_category' => 'All transactions for category :name between :start and :end', - 'journals_in_period_for_tag' => 'All transactions for tag :tag between :start and :end', - 'not_available_demo_user' => 'The feature you try to access is not available to demo users.', - 'exchange_rate_instructions' => 'Asset account "@name" only accepts transactions in @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', - 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', - 'transaction_data' => 'Transaction data', + 'journals_in_period_for_category' => 'Toutes les transactions pour la catégorie :name entre :start et :end', + 'journals_in_period_for_tag' => 'Toutes les transactions de balise :tag entre :start et :end', + 'not_available_demo_user' => 'La fonctionnalité que vous essayez d’accéder n’est pas disponible pour les utilisateurs de la démo.', + 'exchange_rate_instructions' => 'Compte d’actif "@name" n’accepte que les transactions en @native_currency. Si vous souhaitez utiliser @foreign_currency à la place, assurez-vous que le montant en @native_currency est aussi bien connu :', + 'transfer_exchange_rate_instructions' => 'Compte d’actif source "@source_name" n’accepte que les transactions en @source_currency. Compte d’actif "@dest_name" de destination n’accepte que les transactions en @dest_currency. Vous devez fournir le montant transféré correctement dans les deux monnaies.', + 'transaction_data' => 'Données de transaction', // search 'search' => 'Rechercher', - 'search_found_transactions' => 'Number of transactions found:', - 'general_search_error' => 'An error occured while searching. Please check the log files for more information.', - 'search_box' => 'Search', - 'search_box_intro' => 'Welcome to the search function of Firefly III. Enter your search query in the box. Make sure you check out the help file because the search is pretty advanced.', - 'search_error' => 'Error while searching', - 'search_searching' => 'Searching ...', + 'search_found_transactions' => 'Nombre de transactions trouvées :', + 'general_search_error' => 'Une erreur s’est produite lors de la recherche. S’il vous plaît vérifiez les fichiers de log pour plus d’informations.', + 'search_box' => 'Chercher', + 'search_box_intro' => 'Bienvenue à la fonction de recherche de Firefly III. Entrez votre requête de recherche dans la zone. Assurez-vous de consulter le fichier d’aide parce que la recherche est assez avancée.', + 'search_error' => 'Erreur lors de la recherche', + 'search_searching' => 'Recherche ...', // repeat frequencies: - 'repeat_freq_yearly' => 'yearly', + 'repeat_freq_yearly' => 'annuellement', 'repeat_freq_monthly' => 'mensuel', 'weekly' => 'hebdomadaire', 'quarterly' => 'trimestriel', @@ -258,7 +258,7 @@ return [ 'warning_transaction_subset' => 'Pour des raisons de performances cette liste est limitée à :max_num_transactions et peut n\'afficher qu\'une partie des opérations correspondantes', 'warning_no_matching_transactions' => 'Aucunes opérations correspondantes trouvées. Veuillez noter que pour des raisons de performances, seule les dernières :num_transactions opérations ont été vérifiées.', 'warning_no_valid_triggers' => 'Aucun déclencheurs valide fourni.', - 'apply_rule_selection' => 'Apply rule ":title" to a selection of your transactions', + 'apply_rule_selection' => 'Appliquer la règle ":title" à une sélection de vos transactions', 'apply_rule_selection_intro' => 'Rules like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run it on a selection of your existing transactions. This can be useful when you have updated a rule and you need the changes to be applied to all of your other transactions.', 'include_transactions_from_accounts' => 'Iclure les opérations depuis ces comptes', 'applied_rule_selection' => 'Rule ":title" has been applied to your selection.', From 207b0194c24666dcb74c0406427d000d9763820b Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 00:30:07 +0200 Subject: [PATCH 021/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 48 ++++++++++++++++---------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index cca0521221..033683119e 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -259,11 +259,11 @@ return [ 'warning_no_matching_transactions' => 'Aucunes opérations correspondantes trouvées. Veuillez noter que pour des raisons de performances, seule les dernières :num_transactions opérations ont été vérifiées.', 'warning_no_valid_triggers' => 'Aucun déclencheurs valide fourni.', 'apply_rule_selection' => 'Appliquer la règle ":title" à une sélection de vos transactions', - 'apply_rule_selection_intro' => 'Rules like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run it on a selection of your existing transactions. This can be useful when you have updated a rule and you need the changes to be applied to all of your other transactions.', + 'apply_rule_selection_intro' => 'Les règles comme ":title" ne s\'appliquent normalement qu\'aux transactions nouvelles ou mises à jour, mais vous pouvez dire à Firefly III de l’exécuter sur une sélection de vos transactions existantes. Cela peut être utile lorsque vous avez mis à jour une règle et vous avez besoin que les modifications soit appliqué à l’ensemble de vos autres transactions.', 'include_transactions_from_accounts' => 'Iclure les opérations depuis ces comptes', - 'applied_rule_selection' => 'Rule ":title" has been applied to your selection.', + 'applied_rule_selection' => 'La règle ":title" a été appliqué à votre sélection.', 'execute' => 'Executer', - 'apply_rule_group_selection' => 'Apply rule group ":title" to a selection of your transactions', + 'apply_rule_group_selection' => 'Appliquer le groupe de règles ":title" à une sélection de vos transactions', 'apply_rule_group_selection_intro' => 'Rule groups like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run all the rules in this group on a selection of your existing transactions. This can be useful when you have updated a group of rules and you need the changes to be applied to all of your other transactions.', 'applied_rule_group_selection' => 'Rule group ":title" has been applied to your selection.', @@ -278,7 +278,7 @@ return [ 'rule_trigger_to_account_is' => 'Le compte de destination est ":trigger_value"', 'rule_trigger_to_account_contains' => 'Le compte de destination contient ":trigger_value"', 'rule_trigger_transaction_type' => 'L\'opération est du type ":trigger_value"', - 'rule_trigger_category_is' => 'Category is ":trigger_value"', + 'rule_trigger_category_is' => 'La catégorie est ":trigger_value"', 'rule_trigger_amount_less' => 'Le montant est inférieur à :trigger_value', 'rule_trigger_amount_exactly' => 'Le montant est :trigger_value', 'rule_trigger_amount_more' => 'Le montant est supérieur à :trigger_value', @@ -302,11 +302,11 @@ return [ 'rule_trigger_description_ends_choice' => 'La description se termine par..', 'rule_trigger_description_contains_choice' => 'La description contient..', 'rule_trigger_description_is_choice' => 'La description est..', - 'rule_trigger_category_is_choice' => 'Category is..', - 'rule_trigger_budget_is_choice' => 'Budget is..', - 'rule_trigger_tag_is_choice' => '(A) tag is..', - 'rule_trigger_has_attachments_choice' => 'Has at least this many attachments', - 'rule_trigger_has_attachments' => 'Has at least :trigger_value attachment(s)', + 'rule_trigger_category_is_choice' => 'La catégorie est..', + 'rule_trigger_budget_is_choice' => 'Le budget est..', + 'rule_trigger_tag_is_choice' => '(A) le tag est..', + 'rule_trigger_has_attachments_choice' => 'À au moins autant de pièces jointes', + 'rule_trigger_has_attachments' => 'À au moins :trigger_value pièce(s) jointe(s)', 'rule_trigger_store_journal' => 'Lorsqu’une transaction est créée', 'rule_trigger_update_journal' => 'Lorsqu’une transaction est mise à jour', 'rule_action_set_category' => 'Définir la catégorie à ":action_value"', @@ -329,10 +329,10 @@ return [ 'rule_action_set_description_choice' => 'Définir la description à..', 'rule_action_append_description_choice' => 'Suffixer la description avec..', 'rule_action_prepend_description_choice' => 'Préfixer la description avec..', - 'rule_action_set_source_account_choice' => 'Set source account to...', - 'rule_action_set_source_account' => 'Set source account to :action_value', - 'rule_action_set_destination_account_choice' => 'Set destination account to...', - 'rule_action_set_destination_account' => 'Set destination account to :action_value', + 'rule_action_set_source_account_choice' => 'Définissez le compte source à...', + 'rule_action_set_source_account' => 'Définir le compte source à :action_value', + 'rule_action_set_destination_account_choice' => 'Définissez le compte de destination pour...', + 'rule_action_set_destination_account' => 'Définissez le compte de destination pour :action_value', // tags 'store_new_tag' => 'Créer un nouveau tag', @@ -370,9 +370,9 @@ return [ 'pref_two_factor_auth_remove_will_disable' => '(cela désactivera également l\'authentification à deux facteurs)', 'pref_save_settings' => 'Enregistrer les paramètres', 'saved_preferences' => 'Préférences enregistrées!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', + 'preferences_general' => 'Général', + 'preferences_frontpage' => 'Écran d\'accueil', + 'preferences_security' => 'Sécurité', 'preferences_layout' => 'Layout', 'pref_home_show_deposits' => 'Show deposits on the home screen', 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', @@ -839,21 +839,21 @@ return [ 'period' => 'Période', 'balance' => 'Balance', 'summary' => 'Summary', - 'sum' => 'Sum', - 'average' => 'Average', - 'balanceFor' => 'Balance for :name', + 'sum' => 'Somme', + 'average' => 'Moyenne', + 'balanceFor' => 'Balance pour :name', // piggy banks: - 'add_money_to_piggy' => 'Add money to piggy bank ":name"', + 'add_money_to_piggy' => 'Ajouter de l’argent à la tirelire ":name"', 'piggy_bank' => 'Tirelire', - 'new_piggy_bank' => 'New piggy bank', + 'new_piggy_bank' => 'Nouvelle tirelire', 'store_piggy_bank' => 'Créer une nouvelle tirelire', 'stored_piggy_bank' => 'Créer une nouvelle tirelire ":name"', 'account_status' => 'Statut du compte', - 'left_for_piggy_banks' => 'Left for piggy banks', + 'left_for_piggy_banks' => 'Reste pour les tirelires', 'sum_of_piggy_banks' => 'Somme des tirelires', - 'saved_so_far' => 'Saved so far', - 'left_to_save' => 'Left to save', + 'saved_so_far' => 'Enregistré jusqu\'à présent', + 'left_to_save' => 'Reste à sauver', 'suggested_amount' => 'Suggested monthly amount to save', 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', From 7bb5d243a0db302c7058040739bd5c75b9d2a406 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 00:40:07 +0200 Subject: [PATCH 022/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 54 ++++++++++++++++---------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 033683119e..4dfc3802d2 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -373,20 +373,20 @@ return [ 'preferences_general' => 'Général', 'preferences_frontpage' => 'Écran d\'accueil', 'preferences_security' => 'Sécurité', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', + 'preferences_layout' => 'Mise en Page', + 'pref_home_show_deposits' => 'Afficher les dépôts sur l\'écran d\'accueil', + 'pref_home_show_deposits_info' => 'L\'écran d\'accueil affiche déjà vos comptes de dépenses. Devrait-il aussi afficher vos comptes de revenus?', + 'pref_home_do_show_deposits' => 'Oui, montrez-les', + 'successful_count' => 'dont :count avec succès', 'transaction_page_size_title' => 'Taille de la page', 'transaction_page_size_help' => 'N’importe quelle liste de transactions montre au plus ce nombre de transactions', 'transaction_page_size_label' => 'Taille de la page', 'between_dates' => '(:start et :end)', 'pref_optional_fields_transaction' => 'Champs optionnels pour les transactions', - 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', + 'pref_optional_fields_transaction_help' => 'Par défaut, tous les champs ne sont pas activés lors de la création d\'une nouvelle transaction (en raison du désordre). Ci-dessous, vous pouvez activer ces champs si vous pensez qu\'ils pourraient vous être utiles. Bien sûr, tout domaine désactivé, mais déjà rempli, sera visible quel que soit le paramètre.', 'optional_tj_date_fields' => 'Champ date', 'optional_tj_business_fields' => 'Champs professionnels', - 'optional_tj_attachment_fields' => 'Attachment fields', + 'optional_tj_attachment_fields' => 'Champs de pièces jointes', 'pref_optional_tj_interest_date' => 'Date des intérêts', 'pref_optional_tj_book_date' => 'Date de réservation', 'pref_optional_tj_process_date' => 'Date de traitement', @@ -854,38 +854,38 @@ return [ 'sum_of_piggy_banks' => 'Somme des tirelires', 'saved_so_far' => 'Enregistré jusqu\'à présent', 'left_to_save' => 'Reste à sauver', - 'suggested_amount' => 'Suggested monthly amount to save', - 'add_money_to_piggy_title' => 'Add money to piggy bank ":name"', - 'remove_money_from_piggy_title' => 'Remove money from piggy bank ":name"', - 'add' => 'Add', + 'suggested_amount' => 'Montant mensuel suggéré à enregistrer', + 'add_money_to_piggy_title' => 'Ajouter de l’argent à la tirelire ":name"', + 'remove_money_from_piggy_title' => 'Retirer l’argent de la tirelire ":name"', + 'add' => 'Ajouter', - 'remove' => 'Remove', + 'remove' => 'Enlever', 'max_amount_add' => 'Le montant maximum que vous pouvez ajouter est', 'max_amount_remove' => 'Le montant maximum que vous pouvez supprimer est', - 'update_piggy_button' => 'Update piggy bank', - 'update_piggy_title' => 'Update piggy bank ":name"', - 'updated_piggy_bank' => 'Updated piggy bank ":name"', + 'update_piggy_button' => 'Mise à jour tirelire', + 'update_piggy_title' => 'Mise à jour de tirelire ":name"', + 'updated_piggy_bank' => 'Mise à jour de la tirelire ":name"', 'details' => 'Détails', 'events' => 'Evènements', 'target_amount' => 'Montant cible', 'start_date' => 'Date de début', 'target_date' => 'Date cible', 'no_target_date' => 'Aucune date butoir', - 'todo' => 'to do', - 'table' => 'Table', - 'piggy_bank_not_exists' => 'Piggy bank no longer exists.', - 'add_any_amount_to_piggy' => 'Add money to this piggy bank to reach your target of :amount.', - 'add_set_amount_to_piggy' => 'Add :amount to fill this piggy bank on :date', - 'delete_piggy_bank' => 'Delete piggy bank ":name"', - 'cannot_add_amount_piggy' => 'Could not add :amount to ":name".', - 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', + 'todo' => 'à faire', + 'table' => 'Tableau', + 'piggy_bank_not_exists' => 'La tirelire n’existe plus.', + 'add_any_amount_to_piggy' => 'Ajouter de l’argent pour cette tirelire pour atteindre votre objectif de :amount.', + 'add_set_amount_to_piggy' => 'Ajouter :amount pour remplir cette tirelire au :date', + 'delete_piggy_bank' => 'Supprimer la tirelire ":name"', + 'cannot_add_amount_piggy' => 'Impossible d\'ajouter :amount à ":name".', + 'cannot_remove_from_piggy' => 'Impossible de supprimer :amount à ":name".', 'deleted_piggy_bank' => 'Tirelire ":name" supprimée', - 'added_amount_to_piggy' => 'Added :amount to ":name"', - 'removed_amount_from_piggy' => 'Removed :amount from ":name"', - 'cannot_remove_amount_piggy' => 'Could not remove :amount from ":name".', + 'added_amount_to_piggy' => 'Ajouté :amount à ":name"', + 'removed_amount_from_piggy' => 'Supprimé :amount du ":name"', + 'cannot_remove_amount_piggy' => 'Impossible de supprimer :amount de ":name".', // tags - 'regular_tag' => 'Just a regular tag.', + 'regular_tag' => 'Juste une balise ordinaire.', 'balancing_act' => 'Un tag prend au maximum deux opérations : une dépense et un transfert. Ils s\'équilibreront mutuellement.', 'advance_payment' => 'Un tag accepte une dépense et un nombre quelconque de dépôts visant à rembourser la dépense originale.', 'delete_tag' => 'Supprimer le tag ":tag"', From 298e6d38a02097bea1de634f4e44f06586e645b1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 00:50:07 +0200 Subject: [PATCH 023/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 4dfc3802d2..d21d621daa 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -397,7 +397,7 @@ return [ 'pref_optional_tj_notes' => 'Notes', 'pref_optional_tj_attachments' => 'Pièces jointes', 'optional_field_meta_dates' => 'Dates', - 'optional_field_meta_business' => 'Business', + 'optional_field_meta_business' => 'Commerce', 'optional_field_attachments' => 'Pièces jointes', 'optional_field_meta_data' => 'Métadonnées facultatives', @@ -418,9 +418,9 @@ return [ 'password_changed' => 'Mot de passe modifié!', 'should_change' => 'L’idée est de changer votre mot de passe.', 'invalid_password' => 'Mot de passe incorrect!', - 'what_is_pw_security' => 'What is "verify password security"?', - 'secure_pw_title' => 'How to choose a secure password', - 'secure_pw_history' => 'In August 2017 well known security researcher Troy Hunt released a list of 306 million stolen passwords. These passwords were stolen during breakins at companies like LinkedIn, Adobe and NeoPets (and many more).', + 'what_is_pw_security' => 'Qu\'est-ce que "vérifier la sécurité du mot de passe" ?', + 'secure_pw_title' => 'Comment choisir un mot de passe sécurisé', + 'secure_pw_history' => 'En août 2017, le réputé chercheur en sécurité Troy Hunt a publié une liste de 306 millions de mots de passe volés. Ces mots de passe ont été volés lors de cambriolages à des entreprises comme LinkedIn, Adobe et NeoPets (et bien d’autres).', 'secure_pw_check_box' => 'By checking the box, Firefly III will send the SHA1 hash of your password to the website of Troy Hunt to see if it is on the list. This will stop you from using unsafe passwords as is recommended in the latest NIST Special Publication on this subject.', 'secure_pw_sha1' => 'But I thought SHA1 was broken?', 'secure_pw_hash_speed' => 'Yes, but not in this context. As you can read on the website detailing how they broke SHA1, it is now slightly easier to find a "collision": another string that results in the same SHA1-hash. It now only takes 10,000 years using a single-GPU machine.', From ddf0ee997208d9b959eec0f1a7c3c9394f5f1bfe Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 01:00:07 +0200 Subject: [PATCH 024/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index d21d621daa..6a7cd02efc 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -421,11 +421,11 @@ return [ 'what_is_pw_security' => 'Qu\'est-ce que "vérifier la sécurité du mot de passe" ?', 'secure_pw_title' => 'Comment choisir un mot de passe sécurisé', 'secure_pw_history' => 'En août 2017, le réputé chercheur en sécurité Troy Hunt a publié une liste de 306 millions de mots de passe volés. Ces mots de passe ont été volés lors de cambriolages à des entreprises comme LinkedIn, Adobe et NeoPets (et bien d’autres).', - 'secure_pw_check_box' => 'By checking the box, Firefly III will send the SHA1 hash of your password to the website of Troy Hunt to see if it is on the list. This will stop you from using unsafe passwords as is recommended in the latest NIST Special Publication on this subject.', - 'secure_pw_sha1' => 'But I thought SHA1 was broken?', - 'secure_pw_hash_speed' => 'Yes, but not in this context. As you can read on the website detailing how they broke SHA1, it is now slightly easier to find a "collision": another string that results in the same SHA1-hash. It now only takes 10,000 years using a single-GPU machine.', - 'secure_pw_hash_security' => 'This collision would not be equal to your password, nor would it be useful on (a site like) Firefly III. This application does not use SHA1 for password verification. So it is safe to check this box. Your password is hashed and sent over HTTPS.', - 'secure_pw_should' => 'Should I check the box?', + 'secure_pw_check_box' => 'En cochant la case, Firefly III enverra l\'empreinte SHA1 de votre mot de passe au site Web de Troy Hunt pour voir si c’est sur la liste. Cela vous empêchera d\'utiliser des mots de passe dangereux comme cela est recommandé dans les dernières NIST Special Publication à ce sujet.', + 'secure_pw_sha1' => 'Mais je pensais que SHA1 était cassé ?', + 'secure_pw_hash_speed' => 'Oui, mais pas dans ce contexte. Comme vous pouvez le lire sur le site Web détaillant comment ils ont cassé SHA1, c’est maintenant légèrement plus facile de trouver une « collision » : une autre chaîne qui aboutit à la même empreinte SHA1. Maintenant, cela prend seulement 10 000 ans, à l’aide d’une machine mono-GPU.', + 'secure_pw_hash_security' => 'Cette collision ne serait pas égale à votre mot de passe, et ne serait pas utile sur (un site comme) Firefly III. Cette application n\'utilise pas SHA1 pour la vérification du mot de passe. Il est donc sûr de cocher cette case. Votre mot de passe est haché et envoyé par HTTPS.', + 'secure_pw_should' => 'Dois-je cocher la case ?', 'secure_pw_long_password' => 'If you just generated a long, single-use password for Firefly III using some kind of password generator: no.', 'secure_pw_short' => 'If you just entered the password you always use: Please yes.', From 56f7ca388d34e55467fab71852356ca41883d177 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 01:10:06 +0200 Subject: [PATCH 025/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 6a7cd02efc..c0f1154017 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -426,8 +426,8 @@ return [ 'secure_pw_hash_speed' => 'Oui, mais pas dans ce contexte. Comme vous pouvez le lire sur le site Web détaillant comment ils ont cassé SHA1, c’est maintenant légèrement plus facile de trouver une « collision » : une autre chaîne qui aboutit à la même empreinte SHA1. Maintenant, cela prend seulement 10 000 ans, à l’aide d’une machine mono-GPU.', 'secure_pw_hash_security' => 'Cette collision ne serait pas égale à votre mot de passe, et ne serait pas utile sur (un site comme) Firefly III. Cette application n\'utilise pas SHA1 pour la vérification du mot de passe. Il est donc sûr de cocher cette case. Votre mot de passe est haché et envoyé par HTTPS.', 'secure_pw_should' => 'Dois-je cocher la case ?', - 'secure_pw_long_password' => 'If you just generated a long, single-use password for Firefly III using some kind of password generator: no.', - 'secure_pw_short' => 'If you just entered the password you always use: Please yes.', + 'secure_pw_long_password' => 'Si vous venez de générer un long mot de passe unique pour Firefly III à l\'aide d\'un type de générateur de mot de passe : no.', + 'secure_pw_short' => 'Si vous venez d\'entrer le mot de passe que vous utilisez toujours : S\'il vous plaît, oui.', // attachments From 3d58a0c0f39fc63e3c2bd05daf6edd5cb80d681c Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 01:20:07 +0200 Subject: [PATCH 026/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index c0f1154017..43d9fdfdc2 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -597,8 +597,8 @@ return [ 'select_more_than_one_category' => 'Please select more than one category', 'select_more_than_one_budget' => 'Please select more than one budget', 'select_more_than_one_tag' => 'Please select more than one tag', - 'from_to' => 'From :start to :end', - 'from_to_breadcrumb' => 'from :start to :end', + 'from_to' => 'De :start à :end', + 'from_to_breadcrumb' => 'de :start à :end', 'account_default_currency' => 'If you select another currency, new transactions from this account will have this currency pre-selected.', // categories: From 08e5b018b83748b4cfbf85b6160f7b4c9847ca4f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 01:20:08 +0200 Subject: [PATCH 027/668] New translations csv.php (French) --- resources/lang/fr_FR/csv.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/csv.php b/resources/lang/fr_FR/csv.php index afd0dbf930..7a3eb6206f 100644 --- a/resources/lang/fr_FR/csv.php +++ b/resources/lang/fr_FR/csv.php @@ -77,7 +77,7 @@ return [ 'column_sepa-ct-op' => 'SEPA Transfert Crédit compte opposé', 'column_sepa-db' => 'SEPA débit immédiat', 'column_tags-comma' => 'Tags (séparé par des virgules)', - 'column_tags-space' => 'Tags(séparé par des espaces)', + 'column_tags-space' => 'Tags (séparé par des espaces)', 'column_account-number' => 'Compte d’actif (numéro de compte)', 'column_opposing-number' => 'Compte destination (numéro de compte)', ]; \ No newline at end of file From bd8afc67ddc367c8cc7e6bee7882e4729fdcbe2f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 10:00:14 +0200 Subject: [PATCH 028/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 43d9fdfdc2..d4a2409fca 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -449,9 +449,9 @@ return [ 'title_transfers' => 'Transferts', // convert stuff: - 'convert_is_already_type_Withdrawal' => 'This transaction is already a withdrawal', - 'convert_is_already_type_Deposit' => 'This transaction is already a deposit', - 'convert_is_already_type_Transfer' => 'This transaction is already a transfer', + 'convert_is_already_type_Withdrawal' => 'Cette transaction est déjà un retrait', + 'convert_is_already_type_Deposit' => 'Cette transaction est déjà un dépôt', + 'convert_is_already_type_Transfer' => 'Cette transaction est déjà un transfert', 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', 'convert_to_Deposit' => 'Convert ":description" to a deposit', 'convert_to_Transfer' => 'Convert ":description" to a transfer', @@ -461,7 +461,7 @@ return [ 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', - 'transaction_journal_convert_options' => 'Convert this transaction', + 'transaction_journal_convert_options' => 'Convertir cette transaction', 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', From 0b2c3d7ca82b62f89003ad89ffff30579849b7b9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 10:10:22 +0200 Subject: [PATCH 029/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index d4a2409fca..8d02ce65dd 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -452,9 +452,9 @@ return [ 'convert_is_already_type_Withdrawal' => 'Cette transaction est déjà un retrait', 'convert_is_already_type_Deposit' => 'Cette transaction est déjà un dépôt', 'convert_is_already_type_Transfer' => 'Cette transaction est déjà un transfert', - 'convert_to_Withdrawal' => 'Convert ":description" to a withdrawal', - 'convert_to_Deposit' => 'Convert ":description" to a deposit', - 'convert_to_Transfer' => 'Convert ":description" to a transfer', + 'convert_to_Withdrawal' => 'Convertir ":description" vers un retrait', + 'convert_to_Deposit' => 'Convertir ":description" vers un dépôt', + 'convert_to_Transfer' => 'Convertir ":description" vers un transfert', 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', @@ -462,9 +462,9 @@ return [ 'convert_options_TransferWithdrawal' => 'Convert a transfer into a withdrawal', 'convert_options_TransferDeposit' => 'Convert a transfer into a deposit', 'transaction_journal_convert_options' => 'Convertir cette transaction', - 'convert_Withdrawal_to_deposit' => 'Convert this withdrawal to a deposit', - 'convert_Withdrawal_to_transfer' => 'Convert this withdrawal to a transfer', - 'convert_Deposit_to_withdrawal' => 'Convert this deposit to a withdrawal', + 'convert_Withdrawal_to_deposit' => 'Convertir ce retrait en dépôt', + 'convert_Withdrawal_to_transfer' => 'Convertir ce retrait en transfert', + 'convert_Deposit_to_withdrawal' => 'Convertir ce dépôt en retrait', 'convert_Deposit_to_transfer' => 'Convert this deposit to a transfer', 'convert_Transfer_to_deposit' => 'Convert this transfer to a deposit', 'convert_Transfer_to_withdrawal' => 'Convert this transfer to a withdrawal', From 59a7feafef825799a636667d8e3d5c7de8b3ee0f Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 16:30:08 +0200 Subject: [PATCH 030/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index 8d02ce65dd..abf989d52b 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -264,7 +264,7 @@ return [ 'applied_rule_selection' => 'La règle ":title" a été appliqué à votre sélection.', 'execute' => 'Executer', 'apply_rule_group_selection' => 'Appliquer le groupe de règles ":title" à une sélection de vos transactions', - 'apply_rule_group_selection_intro' => 'Rule groups like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run all the rules in this group on a selection of your existing transactions. This can be useful when you have updated a group of rules and you need the changes to be applied to all of your other transactions.', + 'apply_rule_group_selection_intro' => 'Les groupes de règles comme ":titre" ne s\'appliquent normalement qu\'aux transactions nouvelles ou mises à jour, mais vous pouvez dire à Firefly III d\'exécuter toutes les règles de ce groupe sur une sélection de vos transactions existantes. Cela peut être utile lorsque vous avez mis à jour un groupe de règles et que vous avez besoin des modifications à appliquer à toutes vos autres transactions.', 'applied_rule_group_selection' => 'Rule group ":title" has been applied to your selection.', // actions and triggers From 253a98143c2fc400eee235948fb9d1787fac81d8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 16:40:08 +0200 Subject: [PATCH 031/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index abf989d52b..c4728c927e 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -265,7 +265,7 @@ return [ 'execute' => 'Executer', 'apply_rule_group_selection' => 'Appliquer le groupe de règles ":title" à une sélection de vos transactions', 'apply_rule_group_selection_intro' => 'Les groupes de règles comme ":titre" ne s\'appliquent normalement qu\'aux transactions nouvelles ou mises à jour, mais vous pouvez dire à Firefly III d\'exécuter toutes les règles de ce groupe sur une sélection de vos transactions existantes. Cela peut être utile lorsque vous avez mis à jour un groupe de règles et que vous avez besoin des modifications à appliquer à toutes vos autres transactions.', - 'applied_rule_group_selection' => 'Rule group ":title" has been applied to your selection.', + 'applied_rule_group_selection' => 'Le groupe de règles ":title" a été appliqué à votre sélection.', // actions and triggers 'rule_trigger_user_action' => 'L\'action de l’utilisateur est ": trigger_value"', From e5afcbd013b84c97795f601c6ae3aa70b1a76928 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 16:50:07 +0200 Subject: [PATCH 032/668] New translations firefly.php (French) --- resources/lang/fr_FR/firefly.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index c4728c927e..2f342384a0 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -455,7 +455,7 @@ return [ 'convert_to_Withdrawal' => 'Convertir ":description" vers un retrait', 'convert_to_Deposit' => 'Convertir ":description" vers un dépôt', 'convert_to_Transfer' => 'Convertir ":description" vers un transfert', - 'convert_options_WithdrawalDeposit' => 'Convert a withdrawal into a deposit', + 'convert_options_WithdrawalDeposit' => 'Convertir un retrait en dépôt', 'convert_options_WithdrawalTransfer' => 'Convert a withdrawal into a transfer', 'convert_options_DepositTransfer' => 'Convert a deposit into a transfer', 'convert_options_DepositWithdrawal' => 'Convert a deposit into a withdrawal', From 34894fb76b86e2dc29dc791308691ea2ffaa0c81 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 17:26:43 +0200 Subject: [PATCH 033/668] Add some comments [skip ci] --- app/Console/Commands/CreateImport.php | 4 ++++ app/Console/Commands/Import.php | 4 +++- app/Console/Commands/UpgradeDatabase.php | 11 +++++------ app/Console/Commands/UpgradeFireflyInstructions.php | 8 +++++++- app/Console/Commands/UseEncryption.php | 5 ++++- app/Console/Commands/VerifyDatabase.php | 8 ++++++-- 6 files changed, 29 insertions(+), 11 deletions(-) diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php index f155d9add0..191f361582 100644 --- a/app/Console/Commands/CreateImport.php +++ b/app/Console/Commands/CreateImport.php @@ -54,6 +54,8 @@ class CreateImport extends Command } /** + * Run the command. + * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly. */ @@ -133,6 +135,8 @@ class CreateImport extends Command } /** + * Verify user inserts correct arguments. + * * @return bool * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly. */ diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php index f37203cc41..b77dddf0fc 100644 --- a/app/Console/Commands/Import.php +++ b/app/Console/Commands/Import.php @@ -51,7 +51,7 @@ class Import extends Command } /** - * + * Run the import routine. */ public function handle() { @@ -91,6 +91,8 @@ class Import extends Command } /** + * Check if job is valid to be imported. + * * @param ImportJob $job * * @return bool diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 90d2434d76..59065e4d6c 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -26,15 +26,14 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Console\Command; -use Illuminate\Database\Query\JoinClause; use Illuminate\Database\QueryException; use Log; use Preferences; use Schema; -use Steam; /** * Class UpgradeDatabase + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things. * * @package FireflyIII\Console\Commands @@ -74,8 +73,8 @@ class UpgradeDatabase extends Command $this->updateTransferCurrencies(); $this->updateOtherCurrencies(); $this->info('Firefly III database is up to date.'); - return; + return; } @@ -136,8 +135,7 @@ class UpgradeDatabase extends Command } /** - * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon - * the account. + * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. */ public function updateAccountCurrencies(): void { @@ -193,7 +191,7 @@ class UpgradeDatabase extends Command * Both source and destination must match the respective currency preference of the related asset account. * So FF3 must verify all transactions. */ - public function updateOtherCurrencies() + public function updateOtherCurrencies(): void { /** @var CurrencyRepositoryInterface $repository */ $repository = app(CurrencyRepositoryInterface::class); @@ -241,6 +239,7 @@ class UpgradeDatabase extends Command $journal->save(); } ); + return; } diff --git a/app/Console/Commands/UpgradeFireflyInstructions.php b/app/Console/Commands/UpgradeFireflyInstructions.php index 363e5089f7..bed25e3e0e 100644 --- a/app/Console/Commands/UpgradeFireflyInstructions.php +++ b/app/Console/Commands/UpgradeFireflyInstructions.php @@ -84,6 +84,9 @@ class UpgradeFireflyInstructions extends Command } } + /** + * Render instructions. + */ private function installInstructions() { /** @var string $version */ @@ -102,7 +105,7 @@ class UpgradeFireflyInstructions extends Command $this->boxed(''); if (is_null($text)) { - $this->boxed(sprintf('Thank you for installin Firefly III, v%s!', $version)); + $this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version)); $this->boxedInfo('There are no extra installation instructions.'); $this->boxed('Firefly III should be ready for use.'); $this->boxed(''); @@ -131,6 +134,9 @@ class UpgradeFireflyInstructions extends Command } + /** + * Render upgrade instructions. + */ private function updateInstructions() { /** @var string $version */ diff --git a/app/Console/Commands/UseEncryption.php b/app/Console/Commands/UseEncryption.php index 89e6bf012d..5cf80aff32 100644 --- a/app/Console/Commands/UseEncryption.php +++ b/app/Console/Commands/UseEncryption.php @@ -16,6 +16,8 @@ use Illuminate\Support\Str; /** * Class UseEncryption + * + * @package FireflyIII\Console\Commands */ class UseEncryption extends Command { @@ -46,7 +48,6 @@ class UseEncryption extends Command */ public function handle() { - // $this->handleObjects('Account', 'name', 'encrypted'); $this->handleObjects('Bill', 'name', 'name_encrypted'); $this->handleObjects('Bill', 'match', 'match_encrypted'); @@ -57,6 +58,8 @@ class UseEncryption extends Command } /** + * Run each object and encrypt them (or not). + * * @param string $class * @param string $field * @param string $indicator diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php index ffc8f1b667..4dd0848501 100644 --- a/app/Console/Commands/VerifyDatabase.php +++ b/app/Console/Commands/VerifyDatabase.php @@ -204,6 +204,9 @@ class VerifyDatabase extends Command } } + /** + * Report on journals with bad account types linked to them. + */ private function reportIncorrectJournals() { $configuration = [ @@ -270,7 +273,7 @@ class VerifyDatabase extends Command } /** - * + * Report on journals without transactions. */ private function reportNoTransactions() { @@ -288,6 +291,7 @@ class VerifyDatabase extends Command } /** + * Report on things with no linked journals. * @param string $name */ private function reportObject(string $name) @@ -359,7 +363,7 @@ class VerifyDatabase extends Command } /** - * + * Report on transfers that have budgets. */ private function reportTransfersBudgets() { From 7b3ef0e3abfc84063779b10ade759c7edb1046bc Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 17:34:34 +0200 Subject: [PATCH 034/668] Fix #764 --- .../Json/AutoCompleteController.php | 101 ++++++++++++++++++ app/Http/Controllers/JsonController.php | 51 --------- .../Account/FindAccountsTrait.php | 8 +- .../Journal/SupportJournalsTrait.php | 20 +++- routes/web.php | 6 +- 5 files changed, 125 insertions(+), 61 deletions(-) create mode 100644 app/Http/Controllers/Json/AutoCompleteController.php diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php new file mode 100644 index 0000000000..28dc7495d8 --- /dev/null +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -0,0 +1,101 @@ +getAccountsByType( + [AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET] + )->pluck('name')->toArray() + ); + sort($return); + + return Response::json($return); + + } + + /** + * Returns a JSON list of all beneficiaries. + * + * @param AccountRepositoryInterface $repository + * + * @return \Illuminate\Http\JsonResponse + * + */ + public function expenseAccounts(AccountRepositoryInterface $repository) + { + $set = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]); + $filtered = $set->filter( + function (Account $account) { + if ($account->active) { + return $account; + } + + return false; + } + ); + $return = array_unique($filtered->pluck('name')->toArray()); + + sort($return); + + return Response::json($return); + } + + /** + * @param AccountRepositoryInterface $repository + * + * @return \Illuminate\Http\JsonResponse + * + */ + public function revenueAccounts(AccountRepositoryInterface $repository) + { + $set = $repository->getAccountsByType([AccountType::REVENUE]); + $filtered = $set->filter( + function (Account $account) { + if ($account->active) { + return $account; + } + + return false; + } + ); + $return = array_unique($filtered->pluck('name')->toArray()); + sort($return); + + return Response::json($return); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/JsonController.php index 849b0631f9..06a4aa4d1d 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/JsonController.php @@ -62,27 +62,6 @@ class JsonController extends Controller return Response::json(['html' => $view]); } - /** - * Returns a JSON list of all accounts. - * - * @param AccountRepositoryInterface $repository - * - * @return \Illuminate\Http\JsonResponse - * - */ - public function allAccounts(AccountRepositoryInterface $repository) - { - $return = array_unique( - $repository->getAccountsByType( - [AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET] - )->pluck('name')->toArray() - ); - sort($return); - - return Response::json($return); - - } - /** * @param JournalCollectorInterface $collector * @@ -235,36 +214,6 @@ class JsonController extends Controller return Response::json($return); } - /** - * Returns a JSON list of all beneficiaries. - * - * @param AccountRepositoryInterface $repository - * - * @return \Illuminate\Http\JsonResponse - * - */ - public function expenseAccounts(AccountRepositoryInterface $repository) - { - $return = array_unique($repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY])->pluck('name')->toArray()); - sort($return); - - return Response::json($return); - } - - /** - * @param AccountRepositoryInterface $repository - * - * @return \Illuminate\Http\JsonResponse - * - */ - public function revenueAccounts(AccountRepositoryInterface $repository) - { - $return = array_unique($repository->getAccountsByType([AccountType::REVENUE])->pluck('name')->toArray()); - sort($return); - - return Response::json($return); - } - /** * Returns a JSON list of all beneficiaries. * diff --git a/app/Repositories/Account/FindAccountsTrait.php b/app/Repositories/Account/FindAccountsTrait.php index e06a598a47..6bd9a42d45 100644 --- a/app/Repositories/Account/FindAccountsTrait.php +++ b/app/Repositories/Account/FindAccountsTrait.php @@ -205,10 +205,12 @@ trait FindAccountsTrait */ public function getCashAccount(): Account { - $type = AccountType::where('type', AccountType::CASH)->first(); - $account = Account::firstOrCreateEncrypted( - ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1] + $type = AccountType::where('type', AccountType::CASH)->first(); + $account = Account::firstOrCreateEncrypted( + ['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account'] ); + $account->active = true; + $account->save(); return $account; } diff --git a/app/Repositories/Journal/SupportJournalsTrait.php b/app/Repositories/Journal/SupportJournalsTrait.php index df57879971..ee36766599 100644 --- a/app/Repositories/Journal/SupportJournalsTrait.php +++ b/app/Repositories/Journal/SupportJournalsTrait.php @@ -117,8 +117,11 @@ trait SupportJournalsTrait if (strlen($data['source_account_name']) > 0) { $sourceType = AccountType::where('type', 'Revenue account')->first(); $sourceAccount = Account::firstOrCreateEncrypted( - ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1] + ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name']] ); + // always make account active + $sourceAccount->active = true; + $sourceAccount->save(); Log::debug(sprintf('source account name is "%s", account is %d', $data['source_account_name'], $sourceAccount->id)); @@ -132,8 +135,11 @@ trait SupportJournalsTrait $sourceType = AccountType::where('type', AccountType::CASH)->first(); $sourceAccount = Account::firstOrCreateEncrypted( - ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1] + ['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account'] ); + // always make account active + $sourceAccount->active = true; + $sourceAccount->save(); return [ 'source' => $sourceAccount, @@ -161,10 +167,13 @@ trait SupportJournalsTrait 'user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => $data['destination_account_name'], - 'active' => 1, ] ); + // always make account active + $destinationAccount->active = true; + $destinationAccount->save(); + Log::debug(sprintf('destination account name is "%s", account is %d', $data['destination_account_name'], $destinationAccount->id)); return [ @@ -175,8 +184,11 @@ trait SupportJournalsTrait Log::debug('destination_account_name is empty, so default to cash account!'); $destinationType = AccountType::where('type', AccountType::CASH)->first(); $destinationAccount = Account::firstOrCreateEncrypted( - ['user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1] + ['user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account'] ); + // always make account active + $destinationAccount->active = true; + $destinationAccount->save(); return [ 'source' => $sourceAccount, diff --git a/routes/web.php b/routes/web.php index 5557e08cf1..403958be0a 100755 --- a/routes/web.php +++ b/routes/web.php @@ -430,9 +430,9 @@ Route::group( */ Route::group( ['middleware' => 'user-full-auth', 'prefix' => 'json', 'as' => 'json.'], function () { - Route::get('expense-accounts', ['uses' => 'JsonController@expenseAccounts', 'as' => 'expense-accounts']); - Route::get('all-accounts', ['uses' => 'JsonController@allAccounts', 'as' => 'all-accounts']); - Route::get('revenue-accounts', ['uses' => 'JsonController@revenueAccounts', 'as' => 'revenue-accounts']); + Route::get('expense-accounts', ['uses' => 'Json\AutoCompleteController@expenseAccounts', 'as' => 'expense-accounts']); + Route::get('all-accounts', ['uses' => 'Json\AutoCompleteController@allAccounts', 'as' => 'all-accounts']); + Route::get('revenue-accounts', ['uses' => 'Json\AutoCompleteController@revenueAccounts', 'as' => 'revenue-accounts']); Route::get('categories', ['uses' => 'JsonController@categories', 'as' => 'categories']); Route::get('budgets', ['uses' => 'JsonController@budgets', 'as' => 'budgets']); Route::get('tags', ['uses' => 'JsonController@tags', 'as' => 'tags']); From 431bcf20eae0e605eb2c72326010fc7a18f02335 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 17:37:48 +0200 Subject: [PATCH 035/668] Add list button [skip ci] #763 --- resources/views/tags/show.twig | 1 + 1 file changed, 1 insertion(+) diff --git a/resources/views/tags/show.twig b/resources/views/tags/show.twig index a5d7d40287..3237078e90 100644 --- a/resources/views/tags/show.twig +++ b/resources/views/tags/show.twig @@ -182,4 +182,5 @@ + {% endblock %} From 6666d1a2f40a21feec0eb3ea15d21cbbccf23378 Mon Sep 17 00:00:00 2001 From: James Cole Date: Tue, 15 Aug 2017 21:12:49 +0200 Subject: [PATCH 036/668] Add debug information to getAmount routine. --- app/Import/Object/ImportJournal.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Import/Object/ImportJournal.php b/app/Import/Object/ImportJournal.php index 21bd535b8f..33b7db299b 100644 --- a/app/Import/Object/ImportJournal.php +++ b/app/Import/Object/ImportJournal.php @@ -91,20 +91,28 @@ class ImportJournal */ public function getAmount(): string { + Log::debug('Now in getAmount()'); if (is_null($this->convertedAmount)) { + Log::debug('convertedAmount is NULL'); /** @var ConverterInterface $amountConverter */ $amountConverter = app(Amount::class); $this->convertedAmount = $amountConverter->convert($this->amount); + Log::debug(sprintf('First attempt to convert gives "%s"', $this->convertedAmount)); // modify foreach ($this->modifiers as $modifier) { $class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role']))); /** @var ConverterInterface $converter */ $converter = app($class); + Log::debug(sprintf('Now launching converter %s', $class)); if ($converter->convert($modifier['value']) === -1) { $this->convertedAmount = Steam::negative($this->convertedAmount); } + Log::debug(sprintf('convertedAmount after conversion is %s', $this->convertedAmount)); } + + Log::debug(sprintf('After modifiers the result is: "%s"', $this->convertedAmount)); } + Log::debug(sprintf('convertedAmount is: "%s"', $this->convertedAmount)); if (bccomp($this->convertedAmount, '0') === 0) { throw new FireflyException('Amount is zero.'); } From ebb37c09e5d49bcc304ea1a359418f1645942940 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 19:32:05 +0200 Subject: [PATCH 037/668] New translations form.php (Spanish) --- resources/lang/es_ES/form.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 6692bba2ad..3eae0844a8 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -14,8 +14,8 @@ declare(strict_types=1); return [ // new user: - 'bank_name' => 'Bank name', - 'bank_balance' => 'Balance', + 'bank_name' => 'Banco', + 'bank_balance' => 'Saldo', 'savings_balance' => 'Savings balance', 'credit_card_limit' => 'Credit card limit', 'automatch' => 'Match automatically', From a29292e0182e12ab4a7303c8145ebfbc8fb766d8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 19:40:06 +0200 Subject: [PATCH 038/668] New translations form.php (Spanish) --- resources/lang/es_ES/form.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 3eae0844a8..21435d2f63 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -16,16 +16,16 @@ return [ // new user: 'bank_name' => 'Banco', 'bank_balance' => 'Saldo', - 'savings_balance' => 'Savings balance', - 'credit_card_limit' => 'Credit card limit', + 'savings_balance' => 'Salgo de ahorro', + 'credit_card_limit' => 'Límite de la tarjeta de crédito', 'automatch' => 'Match automatically', - 'skip' => 'Skip', - 'name' => 'Name', - 'active' => 'Active', - 'amount_min' => 'Minimum amount', - 'amount_max' => 'Maximum amount', + 'skip' => 'Saltar', + 'name' => 'Nombre', + 'active' => 'Activo', + 'amount_min' => 'Importe mínimo', + 'amount_max' => 'Importe máximo', 'match' => 'Matches on', - 'repeat_freq' => 'Repeats', + 'repeat_freq' => 'Repetición', 'journal_currency_id' => 'Currency', 'currency_id' => 'Currency', 'attachments' => 'Attachments', From 08af0aab751b2ad95628852245542c6c03f46896 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 19:50:06 +0200 Subject: [PATCH 039/668] New translations form.php (Spanish) --- resources/lang/es_ES/form.php | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 21435d2f63..743ff7f1fc 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -26,24 +26,24 @@ return [ 'amount_max' => 'Importe máximo', 'match' => 'Matches on', 'repeat_freq' => 'Repetición', - 'journal_currency_id' => 'Currency', - 'currency_id' => 'Currency', - 'attachments' => 'Attachments', - 'journal_amount' => 'Amount', + 'journal_currency_id' => 'Divisa', + 'currency_id' => 'Divisa', + 'attachments' => 'Adjuntos', + 'journal_amount' => 'Importe', 'journal_asset_source_account' => 'Asset account (source)', 'journal_source_account_name' => 'Revenue account (source)', 'journal_source_account_id' => 'Asset account (source)', 'BIC' => 'BIC', - 'verify_password' => 'Verify password security', - 'account_from_id' => 'From account', - 'account_to_id' => 'To account', - 'source_account' => 'Source account', - 'destination_account' => 'Destination account', + 'verify_password' => 'Verificar la seguridad de contraseña', + 'account_from_id' => 'Cuenta origen', + 'account_to_id' => 'Cuenta destino', + 'source_account' => 'Cuenta origen', + 'destination_account' => 'Cuenta destino', 'journal_destination_account_id' => 'Asset account (destination)', 'asset_destination_account' => 'Asset account (destination)', 'asset_source_account' => 'Asset account (source)', - 'journal_description' => 'Description', - 'note' => 'Notes', + 'journal_description' => 'Descripción', + 'note' => 'Notas', 'split_journal' => 'Split this transaction', 'split_journal_explanation' => 'Split this transaction in multiple parts', 'currency' => 'Currency', From ef21ac3d5ad3e2f90dc7194dae53cf00a386efdd Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 19:50:08 +0200 Subject: [PATCH 040/668] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index f746be2614..87c4704ae1 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -22,14 +22,14 @@ return [ 'everything' => 'Todo', 'customRange' => 'Rango personalizado', 'apply' => 'Aplicar', - 'select_date' => 'Select date..', + 'select_date' => 'Seleccionar fecha...', 'cancel' => 'Cancelar', 'from' => 'Desde', 'to' => 'Hasta', 'showEverything' => 'Mostrar todo', 'never' => 'Nunca', 'search_results_for' => 'Resultados de la búsqueda para %{query}', - 'no_results_for_empty_search' => 'Your search was empty, so nothing was found.', + 'no_results_for_empty_search' => 'Su búsqueda estaba vacía, por lo que no se encontró nada.', 'bounced_error' => 'El mensaje enviado al: correo electrónico no ha sido recibido, así que no hay acceso para usted.', 'deleted_error' => 'Las credenciales no coinciden con los registros.', 'general_blocked_error' => 'Tu cuenta ha sido creada. Ahora puedes iniciar sesión.', From 18bc91734fbca659349a1bf3fb0359ac87e02962 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 20:00:16 +0200 Subject: [PATCH 041/668] New translations firefly.php (Spanish) --- resources/lang/es_ES/firefly.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 87c4704ae1..2a876f7000 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -65,11 +65,11 @@ return [ 'two_factor_forgot_title' => 'Autenticación en dos pasos perdida', 'two_factor_forgot' => 'I forgot my two-factor thing.', 'two_factor_lost_header' => 'Lost your two factor authentication?', - 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', + 'two_factor_lost_intro' => 'Por desgracia, esto no es algo que se pueda restablecer desde la interfaz web. Tienes dos opciones.', 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions.', 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', + 'registered' => '¡Te has registrado con éxito!', 'tagbalancingAct' => 'Balancing act', 'tagadvancePayment' => 'Advance payment', 'tagnothing' => '', From b7522288b504eb8a86fa9abce5a458ab8e01414b Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 20:00:18 +0200 Subject: [PATCH 042/668] New translations breadcrumbs.php (Spanish) --- resources/lang/es_ES/breadcrumbs.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index 5909aac402..fd58a4772c 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -26,7 +26,7 @@ return [ 'edit_bill' => 'Editar factura ":name"', 'delete_bill' => 'Eliminar factura ":name"', 'reports' => 'Reportes', - 'search_result' => 'Search results for ":query"', + 'search_result' => 'Resultados de la búsqueda para ":query"', 'withdrawal_list' => 'Gastos', 'deposit_list' => 'Ganancia, ingresos y depósitos', 'transfer_list' => 'Transferencias', From 9cda8c8bcff731b318cd3fe74a77b7ecc3a93544 Mon Sep 17 00:00:00 2001 From: James Cole Date: Thu, 17 Aug 2017 20:10:13 +0200 Subject: [PATCH 043/668] New translations csv.php (Spanish) --- resources/lang/es_ES/csv.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/es_ES/csv.php b/resources/lang/es_ES/csv.php index b518776e5f..ffdf32c5eb 100644 --- a/resources/lang/es_ES/csv.php +++ b/resources/lang/es_ES/csv.php @@ -15,10 +15,10 @@ return [ // initial config 'initial_title' => 'Import setup (1/3) - Basic CSV import setup', - 'initial_text' => 'To be able to import your file correctly, please validate the options below.', + 'initial_text' => 'Para poder importar correctamente el archivo, por favor comprueba las opciones a continuación.', 'initial_box' => 'Basic CSV import setup', 'initial_box_title' => 'Basic CSV import setup options', - 'initial_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'initial_header_help' => 'Marque aquí si el CSV contiene títulos de columna en la primera fila.', 'initial_date_help' => 'Date time format in your CSV. Follow the format like this page indicates. The default value will parse dates that look like this: :dateExample.', 'initial_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', 'initial_import_account_help' => 'If your CSV file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the CSV belong to.', From 6068cfbd704d44ad0aafcf6d14c8712f7d658c7d Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 08:00:06 +0200 Subject: [PATCH 044/668] New translations intro.php (Dutch) --- resources/lang/nl_NL/intro.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/resources/lang/nl_NL/intro.php b/resources/lang/nl_NL/intro.php index e945adcec8..fc788e515e 100644 --- a/resources/lang/nl_NL/intro.php +++ b/resources/lang/nl_NL/intro.php @@ -11,16 +11,16 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Welcome to the index page of Firefly III. Please take the time to walk through this intro to get a feeling of how Firefly III works.', - 'index_accounts-chart' => 'This chart shows the current balance of your asset accounts. You can select the accounts visible here in your preferences.', - 'index_box_out_holder' => 'This little box and the boxes next to this one will give you a quick overview of your financial situation.', - 'index_help' => 'If you ever need help with a page or a form, press this button.', - 'index_outro' => 'Most pages of Firefly III will start with a little tour like this one. Please contact me when you have questions or comments. Enjoy!', - 'index_sidebar-toggle' => 'To create new transactions, accounts or other things, use the menu under this icon.', + 'index_intro' => 'Welkom op de homepage van Firefly III. Neem even de tijd voor deze introductie zodat je Firefly III leert kennen.', + 'index_accounts-chart' => 'Deze grafiek toont het saldo van je betaalrekening(en). Welke rekeningen zichtbaar zijn kan je aangeven bij de instellingen.', + 'index_box_out_holder' => 'Dit vakje en de vakjes er naast geven een snel overzicht van je financiële situatie.', + 'index_help' => 'Als je ooit hulp nodig hebt, klik dan hier.', + 'index_outro' => 'De meeste pagina\'s in Firefly III beginnen met een kleine rondleiding zoals deze. Zoek me op als je vragen of commentaar hebt. Veel plezier!', + 'index_sidebar-toggle' => 'Nieuwe transacties, rekeningen en andere dingen maak je met het menu onder deze knop.', // create account: - 'accounts_create_iban' => 'Give your accounts a valid IBAN. This could make a data import very easy in the future.', - 'accounts_create_asset_opening_balance' => 'Assets accounts may have an "opening balance", indicating the start of this account\'s history in Firefly.', + 'accounts_create_iban' => 'Geef je rekeningen een geldige IBAN. Dat scheelt met importeren van data.', + 'accounts_create_asset_opening_balance' => 'Betaalrekeningen kunnen een startsaldo hebben, waarmee het begin van deze rekening in Firefly wordt aangegeven.', 'accounts_create_asset_currency' => 'Firefly III supports multiple currencies. Asset accounts have one main currency, which you must set here.', 'accounts_create_asset_virtual' => 'It can sometimes help to give your account a virtual balance: an extra amount always added to or removed from the actual balance.', From b955486f14be247ac19a49980a5133f0103e4725 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 14:45:42 +0200 Subject: [PATCH 045/668] Updated export routine. --- app/Export/Collector/UploadCollector.php | 110 +------- app/Export/Entry/Entry.php | 87 ++++++ app/Export/ExpandedProcessor.php | 308 +++++++++++++++++++++ app/Export/Exporter/CsvExporter.php | 8 +- app/Helpers/Collector/JournalCollector.php | 21 +- app/Http/Controllers/ExportController.php | 16 +- app/Http/Requests/ExportFormRequest.php | 23 +- app/Http/Requests/Request.php | 2 +- app/Models/Transaction.php | 36 +++ resources/views/export/index.twig | 8 +- routes/web.php | 2 +- 11 files changed, 482 insertions(+), 139 deletions(-) create mode 100644 app/Export/ExpandedProcessor.php diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php index 17f863e721..a27de79610 100644 --- a/app/Export/Collector/UploadCollector.php +++ b/app/Export/Collector/UploadCollector.php @@ -15,6 +15,7 @@ namespace FireflyIII\Export\Collector; use Crypt; use Illuminate\Contracts\Encryption\DecryptException; +use Illuminate\Contracts\Filesystem\FileNotFoundException; use Log; use Storage; @@ -50,14 +51,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface public function run(): bool { Log::debug('Going to collect attachments', ['key' => $this->job->key]); - - // file names associated with the old import routine. - $this->vintageFormat = sprintf('csv-upload-%d-', $this->job->user->id); - - // collect old upload files (names beginning with "csv-upload". - $this->collectVintageUploads(); - - // then collect current upload files: $this->collectModernUploads(); return true; @@ -70,7 +63,8 @@ class UploadCollector extends BasicCollector implements CollectorInterface */ private function collectModernUploads(): bool { - $set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']); + $set = $this->job->user->importJobs()->whereIn('status', ['import_complete', 'finished'])->get(['import_jobs.*']); + Log::debug(sprintf('Found %d import jobs', $set->count())); $keys = []; if ($set->count() > 0) { $keys = $set->pluck('key')->toArray(); @@ -83,59 +77,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface return true; } - /** - * This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016. - * - * @return bool - */ - private function collectVintageUploads(): bool - { - // grab upload directory. - $files = $this->uploadDisk->files(); - - foreach ($files as $entry) { - $this->processVintageUpload($entry); - } - - return true; - } - - /** - * This method tells you when the vintage upload file was actually uploaded. - * - * @param string $entry - * - * @return string - */ - private function getVintageUploadDate(string $entry): string - { - // this is an original upload. - $parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry)); - $originalUpload = intval($parts[1]); - $date = date('Y-m-d \a\t H-i-s', $originalUpload); - - return $date; - } - - /** - * Tells you if a file name is a vintage upload. - * - * @param string $entry - * - * @return bool - */ - private function isVintageImport(string $entry): bool - { - $len = strlen($this->vintageFormat); - // file is part of the old import routine: - if (substr($entry, 0, $len) === $this->vintageFormat) { - - return true; - } - - return false; - } - /** * @param string $key * @@ -153,7 +94,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface $content = ''; try { $content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key))); - } catch (DecryptException $e) { + } catch (FileNotFoundException | DecryptException $e) { Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage())); } @@ -168,47 +109,4 @@ class UploadCollector extends BasicCollector implements CollectorInterface return true; } - /** - * If the file is a vintage upload, process it. - * - * @param string $entry - * - * @return bool - */ - private function processVintageUpload(string $entry): bool - { - if ($this->isVintageImport($entry)) { - $this->saveVintageImportFile($entry); - - return true; - } - - return false; - } - - - /** - * This will store the content of the old vintage upload somewhere. - * - * @param string $entry - */ - private function saveVintageImportFile(string $entry) - { - $content = ''; - try { - $content = Crypt::decrypt($this->uploadDisk->get($entry)); - } catch (DecryptException $e) { - Log::error('Could not decrypt old CSV import file ' . $entry . '. Skipped because ' . $e->getMessage()); - } - - if (strlen($content) > 0) { - // add to export disk. - $date = $this->getVintageUploadDate($entry); - $file = $this->job->key . '-Old import dated ' . $date . '.csv'; - $this->exportDisk->put($file, $content); - $this->getEntries()->push($file); - } - } - - } diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index 81ef189d36..817b71ce43 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace FireflyIII\Export\Entry; +use FireflyIII\Models\Transaction; use Steam; /** @@ -37,24 +38,43 @@ final class Entry { // @formatter:off public $journal_id; + public $transaction_id = 0; + public $date; public $description; public $currency_code; public $amount; + public $foreign_currency_code = ''; + public $foreign_amount = '0'; public $transaction_type; public $asset_account_id; public $asset_account_name; + public $asset_account_iban; + public $asset_account_bic; + public $asset_account_number; + public $asset_currency_code; public $opposing_account_id; public $opposing_account_name; + public $opposing_account_iban; + public $opposing_account_bic; + public $opposing_account_number; + public $opposing_currency_code; public $budget_id; public $budget_name; + public $category_id; public $category_name; + + public $bill_id; + public $bill_name; + + public $notes; + public $tags; // @formatter:on /** @@ -95,5 +115,72 @@ final class Entry return $entry; } + /** + * Converts a given transaction (as collected by the collector) into an export entry. + * + * @param Transaction $transaction + * + * @return Entry + */ + public static function fromTransaction(Transaction $transaction): Entry + { + $entry = new self; + $entry->journal_id = $transaction->journal_id; + $entry->transaction_id = $transaction->id; + $entry->date = $transaction->date->format('Ymd'); + $entry->description = $transaction->description; + if (strlen(strval($transaction->transaction_description)) > 0) { + $entry->description = $transaction->transaction_description . '(' . $transaction->description . ')'; + } + $entry->currency_code = $transaction->transactionCurrency->code; + $entry->amount = round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places); + + $entry->foreign_currency_code = is_null($transaction->foreign_currency_id) ? null : $transaction->foreignCurrency->code; + $entry->foreign_amount = is_null($transaction->foreign_currency_id) + ? null + : round( + $transaction->transaction_foreign_amount, $transaction->foreignCurrency->decimal_places + ); + + $entry->transaction_type = $transaction->transaction_type_type; + $entry->asset_account_id = $transaction->account_id; + $entry->asset_account_name = app('steam')->tryDecrypt($transaction->account_name); + $entry->asset_account_iban = $transaction->account_iban; + $entry->asset_account_number = $transaction->account_number; + $entry->asset_account_bic = $transaction->account_bic; + // asset_currency_code + $entry->opposing_account_id = $transaction->opposing_account_id; + $entry->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name); + $entry->opposing_account_iban = $transaction->opposing_account_iban; + $entry->opposing_account_number = $transaction->opposing_account_number; + $entry->opposing_account_bic = $transaction->opposing_account_bic; + // opposing currency code + + /** budget */ + $entry->budget_id = $transaction->transaction_budget_id; + $entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_budget_name); + if (is_null($transaction->transaction_budget_id)) { + $entry->budget_id = $transaction->transaction_journal_budget_id; + $entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name); + } + + /** category */ + $entry->category_id = $transaction->transaction_category_id; + $entry->category_name = app('steam')->tryDecrypt($transaction->transaction_category_name); + if (is_null($transaction->transaction_category_id)) { + $entry->category_id = $transaction->transaction_journal_category_id; + $entry->category_name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name); + } + + /** budget */ + $entry->bill_id = $transaction->bill_id; + $entry->bill_name = app('steam')->tryDecrypt($transaction->bill_name); + + $entry->tags = $transaction->tags; + $entry->notes = $transaction->notes; + + return $entry; + } + } diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php new file mode 100644 index 0000000000..703963f27d --- /dev/null +++ b/app/Export/ExpandedProcessor.php @@ -0,0 +1,308 @@ +journals = new Collection; + $this->exportEntries = new Collection; + $this->files = new Collection; + } + + /** + * @return bool + */ + public function collectAttachments(): bool + { + /** @var AttachmentCollector $attachmentCollector */ + $attachmentCollector = app(AttachmentCollector::class); + $attachmentCollector->setJob($this->job); + $attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']); + $attachmentCollector->run(); + $this->files = $this->files->merge($attachmentCollector->getEntries()); + + return true; + } + + /** + * @return bool + */ + public function collectJournals(): bool + { + // use journal collector thing. + /** @var JournalCollectorInterface $collector */ + $collector = app(JournalCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->settings['startDate'], $this->settings['endDate']) + ->withOpposingAccount()->withBudgetInformation()->withCategoryInformation() + ->removeFilter(InternalTransferFilter::class); + $transactions = $collector->getJournals(); + // get some more meta data for each entry: + $ids = $transactions->pluck('journal_id')->toArray(); + $assetIds = $transactions->pluck('account_id')->toArray(); + $opposingIds = $transactions->pluck('opposing_account_id')->toArray(); + $notes = $this->getNotes($ids); + $tags = $this->getTags($ids); + $ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds); + $transactions->each( + function (Transaction $transaction) use ($notes, $tags, $ibans) { + $journalId = intval($transaction->journal_id); + $accountId = intval($transaction->account_id); + $opposingId = intval($transaction->opposing_account_id); + $transaction->notes = $notes[$journalId] ?? ''; + $transaction->tags = join(',', $tags[$journalId] ?? []); + $transaction->account_number = $ibans[$accountId]['accountNumber'] ?? ''; + $transaction->account_bic = $ibans[$accountId]['BIC'] ?? ''; + $transaction->opposing_account_number = $ibans[$opposingId]['accountNumber'] ?? ''; + $transaction->opposing_account_bic = $ibans[$opposingId]['BIC'] ?? ''; + + } + ); + + $this->journals = $transactions; + + return true; + } + + /** + * @return bool + */ + public function collectOldUploads(): bool + { + /** @var UploadCollector $uploadCollector */ + $uploadCollector = app(UploadCollector::class); + $uploadCollector->setJob($this->job); + $uploadCollector->run(); + + $this->files = $this->files->merge($uploadCollector->getEntries()); + + return true; + } + + /** + * @return bool + */ + public function convertJournals(): bool + { + $this->journals->each( + function (Transaction $transaction) { + $this->exportEntries->push(Entry::fromTransaction($transaction)); + } + ); + Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count())); + + return true; + } + + /** + * @return bool + * @throws FireflyException + */ + public function createZipFile(): bool + { + $zip = new ZipArchive; + $file = $this->job->key . '.zip'; + $fullPath = storage_path('export') . '/' . $file; + + if ($zip->open($fullPath, ZipArchive::CREATE) !== true) { + throw new FireflyException('Cannot store zip file.'); + } + // for each file in the collection, add it to the zip file. + $disk = Storage::disk('export'); + foreach ($this->getFiles() as $entry) { + // is part of this job? + $zipFileName = str_replace($this->job->key . '-', '', $entry); + $zip->addFromString($zipFileName, $disk->get($entry)); + } + + $zip->close(); + + // delete the files: + $this->deleteFiles(); + + return true; + } + + /** + * @return bool + */ + public function exportJournals(): bool + { + $exporterClass = config('firefly.export_formats.' . $this->exportFormat); + $exporter = app($exporterClass); + $exporter->setJob($this->job); + $exporter->setEntries($this->exportEntries); + $exporter->run(); + $this->files->push($exporter->getFileName()); + + return true; + } + + /** + * @return Collection + */ + public function getFiles(): Collection + { + return $this->files; + } + + /** + * Save export job settings to class. + * + * @param array $settings + */ + public function setSettings(array $settings) + { + // save settings + $this->settings = $settings; + $this->accounts = $settings['accounts']; + $this->exportFormat = $settings['exportFormat']; + $this->includeAttachments = $settings['includeAttachments']; + $this->includeOldUploads = $settings['includeOldUploads']; + $this->job = $settings['job']; + } + + /** + * + */ + private function deleteFiles() + { + $disk = Storage::disk('export'); + foreach ($this->getFiles() as $file) { + $disk->delete($file); + } + } + + /** + * Get all IBAN / SWIFT / account numbers + * + * @param array $array + * + * @return array + */ + private function getIbans(array $array): array + { + $array = array_unique($array); + $return = []; + $set = AccountMeta::whereIn('account_id', $array) + ->leftJoin('accounts', 'accounts.id', 'account_meta.account_id') + ->where('accounts.user_id', $this->job->user_id) + ->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id']) + ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']); + /** @var AccountMeta $meta */ + foreach ($set as $meta) { + $id = intval($meta->account_id); + $return[$id][$meta->name] = $meta->data; + } + + return $return; + } + + /** + * Returns, if present, for the given journal ID's the notes. + * + * @param array $array + * + * @return array + */ + private function getNotes(array $array): array + { + $array = array_unique($array); + $set = TransactionJournalMeta::whereIn('journal_meta.transaction_journal_id', $array) + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') + ->where('transaction_journals.user_id', $this->job->user_id) + ->where('journal_meta.name', 'notes')->get( + ['journal_meta.transaction_journal_id', 'journal_meta.data', 'journal_meta.id'] + ); + $return = []; + /** @var TransactionJournalMeta $meta */ + foreach ($set as $meta) { + $id = intval($meta->transaction_journal_id); + $return[$id] = $meta->data; + } + + return $return; + } + + /** + * Returns a comma joined list of all the users tags linked to these journals. + * + * @param array $array + * + * @return array + */ + private function getTags(array $array): array + { + $set = DB::table('tag_transaction_journal') + ->whereIn('tag_transaction_journal.transaction_journal_id', $array) + ->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id') + ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id') + ->where('transaction_journals.user_id', $this->job->user_id) + ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']); + $result = []; + foreach ($set as $entry) { + $id = intval($entry->transaction_journal_id); + $result[$id] = isset($result[$id]) ? $result[$id] : []; + $result[$id][] = Crypt::decrypt($entry->tag); + } + + return $result; + } +} \ No newline at end of file diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php index 34a56182e0..dea7519fa7 100644 --- a/app/Export/Exporter/CsvExporter.php +++ b/app/Export/Exporter/CsvExporter.php @@ -58,8 +58,12 @@ class CsvExporter extends BasicExporter implements ExporterInterface // get field names for header row: $first = $this->getEntries()->first(); - $headers = array_keys(get_object_vars($first)); - $rows[] = $headers; + $headers = []; + if (!is_null($first)) { + $headers = array_keys(get_object_vars($first)); + } + + $rows[] = $headers; /** @var Entry $entry */ foreach ($this->getEntries() as $entry) { diff --git a/app/Helpers/Collector/JournalCollector.php b/app/Helpers/Collector/JournalCollector.php index c31295c235..d892cefdf8 100644 --- a/app/Helpers/Collector/JournalCollector.php +++ b/app/Helpers/Collector/JournalCollector.php @@ -31,7 +31,6 @@ use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\User; -use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; use Illuminate\Database\Query\JoinClause; use Illuminate\Pagination\LengthAwarePaginator; @@ -86,7 +85,9 @@ class JournalCollector implements JournalCollectorInterface 'accounts.name as account_name', 'accounts.encrypted as account_encrypted', + 'accounts.iban as account_iban', 'account_types.type as account_type', + ]; /** @var bool */ private $filterTransfers = false; @@ -175,12 +176,10 @@ class JournalCollector implements JournalCollectorInterface if (!is_null($transaction->bill_name)) { $transaction->bill_name = Steam::decrypt(intval($transaction->bill_name_encrypted), $transaction->bill_name); } + $transaction->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name); + $transaction->account_iban = app('steam')->tryDecrypt($transaction->account_iban); + $transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban); - try { - $transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name); - } catch (DecryptException $e) { - // if this fails its already decrypted. - } } ); @@ -677,10 +676,12 @@ class JournalCollector implements JournalCollectorInterface $this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id'); $this->query->whereNull('opposing.deleted_at'); - $this->fields[] = 'opposing.id as opposing_id'; - $this->fields[] = 'opposing.account_id as opposing_account_id'; - $this->fields[] = 'opposing_accounts.name as opposing_account_name'; - $this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted'; + $this->fields[] = 'opposing.id as opposing_id'; + $this->fields[] = 'opposing.account_id as opposing_account_id'; + $this->fields[] = 'opposing_accounts.name as opposing_account_name'; + $this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted'; + $this->fields[] = 'opposing_accounts.iban as opposing_account_iban'; + $this->fields[] = 'opposing_account_types.type as opposing_account_type'; $this->joinedOpposing = true; Log::debug('joinedOpposing is now true!'); diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 6bb4cd3f44..326ad87c77 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -17,6 +17,7 @@ namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use ExpandedForm; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Export\ExpandedProcessor; use FireflyIII\Export\ProcessorInterface; use FireflyIII\Http\Requests\ExportFormRequest; use FireflyIII\Models\AccountType; @@ -137,34 +138,40 @@ class ExportController extends Controller public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs) { $job = $jobs->findByKey($request->get('job')); + $accounts = $request->get('accounts') ?? []; $settings = [ - 'accounts' => $repository->getAccountsById($request->get('accounts')), + 'accounts' => $repository->getAccountsById($accounts), 'startDate' => new Carbon($request->get('export_start_range')), 'endDate' => new Carbon($request->get('export_end_range')), 'exportFormat' => $request->get('exportFormat'), - 'includeAttachments' => intval($request->get('include_attachments')) === 1, - 'includeOldUploads' => intval($request->get('include_old_uploads')) === 1, + 'includeAttachments' => $request->boolean('include_attachments'), + 'includeOldUploads' => $request->boolean('include_old_uploads'), 'job' => $job, ]; $jobs->changeStatus($job, 'export_status_make_exporter'); /** @var ProcessorInterface $processor */ - $processor = app(ProcessorInterface::class); + $processor = app(ExpandedProcessor::class); $processor->setSettings($settings); + + + /* * Collect journals: */ $jobs->changeStatus($job, 'export_status_collecting_journals'); $processor->collectJournals(); $jobs->changeStatus($job, 'export_status_collected_journals'); + /* * Transform to exportable entries: */ $jobs->changeStatus($job, 'export_status_converting_to_export_format'); $processor->convertJournals(); $jobs->changeStatus($job, 'export_status_converted_to_export_format'); + /* * Transform to (temporary) file: */ @@ -180,6 +187,7 @@ class ExportController extends Controller $jobs->changeStatus($job, 'export_status_collected_attachments'); } + /* * Collect old uploads */ diff --git a/app/Http/Requests/ExportFormRequest.php b/app/Http/Requests/ExportFormRequest.php index 16a5638302..fac7f1ee2c 100644 --- a/app/Http/Requests/ExportFormRequest.php +++ b/app/Http/Requests/ExportFormRequest.php @@ -38,20 +38,19 @@ class ExportFormRequest extends Request public function rules() { $sessionFirst = clone session('first'); - - $first = $sessionFirst->subDay()->format('Y-m-d'); - $today = Carbon::create()->addDay()->format('Y-m-d'); - $formats = join(',', array_keys(config('firefly.export_formats'))); + $first = $sessionFirst->subDay()->format('Y-m-d'); + $today = Carbon::create()->addDay()->format('Y-m-d'); + $formats = join(',', array_keys(config('firefly.export_formats'))); return [ - 'export_start_range' => 'required|date|after:' . $first, - 'export_end_range' => 'required|date|before:' . $today, - 'accounts' => 'required', - 'job' => 'required|belongsToUser:export_jobs,key', - 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', - 'include_attachments' => 'in:0,1', - 'include_config' => 'in:0,1', - 'exportFormat' => 'in:' . $formats, +// 'export_start_range' => 'required|date|after:' . $first, +// 'export_end_range' => 'required|date|before:' . $today, +// 'accounts' => 'required', +// 'job' => 'required|belongsToUser:export_jobs,key', +// 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', +// 'include_attachments' => 'in:0,1', +// 'include_config' => 'in:0,1', +// 'exportFormat' => 'in:' . $formats, ]; } } diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 16e77bad4a..87e1bf2934 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -28,7 +28,7 @@ class Request extends FormRequest * * @return bool */ - protected function boolean(string $field): bool + public function boolean(string $field): bool { return intval($this->input($field)) === 1; } diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index c9b139d485..2b9deae872 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -22,6 +22,42 @@ use Watson\Validating\ValidatingTrait; /** * Class Transaction * + * @property-read int $journal_id + * @property-read Carbon $date + * @property-read string $transaction_description + * @property-read string $transaction_amount + * @property-read string $transaction_foreign_amount + * @property-read string $transaction_type_type + * + * @property-read int $account_id + * @property-read string $account_name + * @property string $account_iban + * @property string $account_number + * @property string $account_bic + * + * @property-read int $opposing_account_id + * @property string $opposing_account_name + * @property string $opposing_account_iban + * @property string $opposing_account_number + * @property string $opposing_account_bic + * + * + * @property-read int $transaction_budget_id + * @property-read string $transaction_budget_name + * @property-read int $transaction_journal_budget_id + * @property-read string $transaction_journal_budget_name + * + * @property-read int $transaction_category_id + * @property-read string $transaction_category_name + * @property-read int $transaction_journal_category_id + * @property-read string $transaction_journal_category_name + * + * @property-read int $bill_id + * @property string $bill_name + * + * @property string $notes + * @property string $tags + * * @package FireflyIII\Models */ class Transaction extends Model diff --git a/resources/views/export/index.twig b/resources/views/export/index.twig index 3ebcc538ea..2f7aaba776 100644 --- a/resources/views/export/index.twig +++ b/resources/views/export/index.twig @@ -8,7 +8,7 @@ -
+ @@ -87,7 +87,9 @@ @@ -102,7 +104,7 @@ - + {% endblock %} {% block styles %} diff --git a/routes/web.php b/routes/web.php index 403958be0a..0b0cba2265 100755 --- a/routes/web.php +++ b/routes/web.php @@ -200,7 +200,7 @@ Route::group( Route::get('status/{jobKey}', ['uses' => 'ExportController@getStatus', 'as' => 'status']); Route::get('download/{jobKey}', ['uses' => 'ExportController@download', 'as' => 'download']); - Route::post('submit', ['uses' => 'ExportController@postIndex', 'as' => 'export']); + Route::post('submit', ['uses' => 'ExportController@postIndex', 'as' => 'submit']); } ); From 7d8876f03ccd3d59d8aa4759cd0fa0951381a8f0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 15:14:44 +0200 Subject: [PATCH 046/668] new export routine --- app/Console/Commands/UpgradeDatabase.php | 23 +++++++++---- app/Export/Entry/Entry.php | 5 +-- app/Export/ExpandedProcessor.php | 33 ++++++++++++++++++- app/Models/Transaction.php | 2 ++ .../Currency/CurrencyRepository.php | 10 ++++++ .../Currency/CurrencyRepositoryInterface.php | 7 ++++ 6 files changed, 70 insertions(+), 10 deletions(-) diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index 59065e4d6c..b70011a8d9 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -27,6 +27,7 @@ use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Console\Command; use Illuminate\Database\QueryException; +use Illuminate\Support\Collection; use Log; use Preferences; use Schema; @@ -262,15 +263,23 @@ class UpgradeDatabase extends Command $set->each( function (TransactionJournal $transfer) use ($repository) { - /** @var Transaction $transaction */ - $transaction = $transfer->transactions()->where('amount', '<', 0)->first(); - $this->updateTransactionCurrency($transaction); - $this->updateJournalCurrency($transaction); + /** @var Collection $transactions */ + $transactions = $transfer->transactions()->where('amount', '<', 0)->get(); + $transactions->each( + function (Transaction $transaction) { + $this->updateTransactionCurrency($transaction); + $this->updateJournalCurrency($transaction); + } + ); - /** @var Transaction $transaction */ - $transaction = $transfer->transactions()->where('amount', '>', 0)->first(); - $this->updateTransactionCurrency($transaction); + /** @var Collection $transactions */ + $transactions = $transfer->transactions()->where('amount', '>', 0)->get(); + $transactions->each( + function (Transaction $transaction) { + $this->updateTransactionCurrency($transaction); + } + ); } ); } diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php index 817b71ce43..b652d9ad66 100644 --- a/app/Export/Entry/Entry.php +++ b/app/Export/Entry/Entry.php @@ -148,13 +148,14 @@ final class Entry $entry->asset_account_iban = $transaction->account_iban; $entry->asset_account_number = $transaction->account_number; $entry->asset_account_bic = $transaction->account_bic; - // asset_currency_code + $entry->asset_currency_code = $transaction->account_currency_code; + $entry->opposing_account_id = $transaction->opposing_account_id; $entry->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name); $entry->opposing_account_iban = $transaction->opposing_account_iban; $entry->opposing_account_number = $transaction->opposing_account_number; $entry->opposing_account_bic = $transaction->opposing_account_bic; - // opposing currency code + $entry->opposing_currency_code = $transaction->opposing_currency_code; /** budget */ $entry->budget_id = $transaction->transaction_budget_id; diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php index 703963f27d..569ff55d9d 100644 --- a/app/Export/ExpandedProcessor.php +++ b/app/Export/ExpandedProcessor.php @@ -25,6 +25,7 @@ use FireflyIII\Models\AccountMeta; use FireflyIII\Models\ExportJob; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournalMeta; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use Illuminate\Support\Collection; use Log; use Storage; @@ -101,17 +102,22 @@ class ExpandedProcessor implements ProcessorInterface $notes = $this->getNotes($ids); $tags = $this->getTags($ids); $ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds); + $currencies = $this->getAccountCurrencies($ibans); $transactions->each( - function (Transaction $transaction) use ($notes, $tags, $ibans) { + function (Transaction $transaction) use ($notes, $tags, $ibans, $currencies) { $journalId = intval($transaction->journal_id); $accountId = intval($transaction->account_id); $opposingId = intval($transaction->opposing_account_id); + $currencyId = $ibans[$accountId]['currency_id'] ?? 0; + $opposingCurrencyId = $ibans[$opposingId]['currency_id'] ?? 0; $transaction->notes = $notes[$journalId] ?? ''; $transaction->tags = join(',', $tags[$journalId] ?? []); $transaction->account_number = $ibans[$accountId]['accountNumber'] ?? ''; $transaction->account_bic = $ibans[$accountId]['BIC'] ?? ''; + $transaction->account_currency_code = $currencies[$currencyId] ?? ''; $transaction->opposing_account_number = $ibans[$opposingId]['accountNumber'] ?? ''; $transaction->opposing_account_bic = $ibans[$opposingId]['BIC'] ?? ''; + $transaction->opposing_currency_code = $currencies[$opposingCurrencyId] ?? ''; } ); @@ -230,6 +236,31 @@ class ExpandedProcessor implements ProcessorInterface } } + /** + * @param array $array + * + * @return array + */ + private function getAccountCurrencies(array $array): array + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $return = []; + $ids = []; + $repository->setUser($this->job->user); + foreach ($array as $value) { + $ids[] = $value['currency_id'] ?? 0; + } + $ids = array_unique($ids); + $result = $repository->getByIds($ids); + + foreach ($result as $currency) { + $return[$currency->id] = $currency->code; + } + + return $return; + } + /** * Get all IBAN / SWIFT / account numbers * diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 2b9deae872..1437532083 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -34,12 +34,14 @@ use Watson\Validating\ValidatingTrait; * @property string $account_iban * @property string $account_number * @property string $account_bic + * @property string $account_currency_code * * @property-read int $opposing_account_id * @property string $opposing_account_name * @property string $opposing_account_iban * @property string $opposing_account_number * @property string $opposing_account_bic + * @property string $opposing_currency_code * * * @property-read int $transaction_budget_id diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 6e0ae64ae7..d86adea16c 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -166,6 +166,16 @@ class CurrencyRepository implements CurrencyRepositoryInterface return TransactionCurrency::get(); } + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids): Collection + { + return TransactionCurrency::whereIn('id', $ids)->get(); + } + /** * @param Preference $preference * diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 6c2ccdd61b..07b953aeca 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -90,6 +90,13 @@ interface CurrencyRepositoryInterface */ public function get(): Collection; + /** + * @param array $ids + * + * @return Collection + */ + public function getByIds(array $ids): Collection; + /** * @param Preference $preference * From 9b177151757f15f85a9e0d19922a37e8819bcab7 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 15:32:11 +0200 Subject: [PATCH 047/668] Fix the unit tests. --- app/Console/Commands/UpgradeDatabase.php | 9 +- app/Http/Controllers/ExportController.php | 5 +- app/Models/ExportJob.php | 3 + app/Providers/FireflyServiceProvider.php | 5 +- database/factories/ModelFactory.php | 1 + .../Controllers/ExportControllerTest.php | 7 +- .../Json/AutoCompleteControllerTest.php | 87 +++++++++++++++++++ .../Controllers/JsonControllerTest.php | 54 ------------ 8 files changed, 101 insertions(+), 70 deletions(-) create mode 100644 tests/Feature/Controllers/Json/AutoCompleteControllerTest.php diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php index b70011a8d9..bf11fad82d 100644 --- a/app/Console/Commands/UpgradeDatabase.php +++ b/app/Console/Commands/UpgradeDatabase.php @@ -71,6 +71,7 @@ class UpgradeDatabase extends Command $this->setTransactionIdentifier(); $this->migrateRepetitions(); $this->updateAccountCurrencies(); + $this->line('Updating currency information..'); $this->updateTransferCurrencies(); $this->updateOtherCurrencies(); $this->info('Firefly III database is up to date.'); @@ -218,16 +219,10 @@ class UpgradeDatabase extends Command if (is_null($transaction->transaction_currency_id)) { $transaction->transaction_currency_id = $currency->id; $transaction->save(); - $this->line(sprintf('Transaction #%d is set to %s', $transaction->id, $currency->code)); } // when mismatch in transaction: if ($transaction->transaction_currency_id !== $currency->id) { - $this->line( - sprintf( - 'Transaction #%d is set to %s and foreign %s', $transaction->id, $currency->code, $transaction->transactionCurrency->code - ) - ); $transaction->foreign_currency_id = $transaction->transaction_currency_id; $transaction->foreign_amount = $transaction->amount; $transaction->transaction_currency_id = $currency->id; @@ -372,12 +367,10 @@ class UpgradeDatabase extends Command if (is_null($transaction->transaction_currency_id)) { $transaction->transaction_currency_id = $currency->id; $transaction->save(); - $this->line(sprintf('Transaction #%d is set to %s', $transaction->id, $currency->code)); } // when mismatch in transaction: if ($transaction->transaction_currency_id !== $currency->id) { - $this->line(sprintf('Transaction #%d is set to %s and foreign %s', $transaction->id, $currency->code, $transaction->transactionCurrency->code)); $transaction->foreign_currency_id = $transaction->transaction_currency_id; $transaction->foreign_amount = $transaction->amount; $transaction->transaction_currency_id = $currency->id; diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php index 326ad87c77..0afaba639f 100644 --- a/app/Http/Controllers/ExportController.php +++ b/app/Http/Controllers/ExportController.php @@ -152,12 +152,9 @@ class ExportController extends Controller $jobs->changeStatus($job, 'export_status_make_exporter'); /** @var ProcessorInterface $processor */ - $processor = app(ExpandedProcessor::class); + $processor = app(ProcessorInterface::class); $processor->setSettings($settings); - - - /* * Collect journals: */ diff --git a/app/Models/ExportJob.php b/app/Models/ExportJob.php index 89f2e4554b..6cf10bbb39 100644 --- a/app/Models/ExportJob.php +++ b/app/Models/ExportJob.php @@ -13,12 +13,15 @@ declare(strict_types=1); namespace FireflyIII\Models; +use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class ExportJob * + * @property User $user + * * @package FireflyIII\Models */ class ExportJob extends Model diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index a2b6429f20..2323ba9340 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -13,7 +13,7 @@ declare(strict_types=1); namespace FireflyIII\Providers; -use FireflyIII\Export\Processor; +use FireflyIII\Export\ExpandedProcessor; use FireflyIII\Export\ProcessorInterface; use FireflyIII\Generator\Chart\Basic\ChartJsGenerator; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; @@ -138,7 +138,8 @@ class FireflyServiceProvider extends ServiceProvider ); // other generators - $this->app->bind(ProcessorInterface::class, Processor::class); + // export: + $this->app->bind(ProcessorInterface::class, ExpandedProcessor::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); $this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class); diff --git a/database/factories/ModelFactory.php b/database/factories/ModelFactory.php index c014cb7078..8669ccf1b3 100644 --- a/database/factories/ModelFactory.php +++ b/database/factories/ModelFactory.php @@ -210,6 +210,7 @@ $factory->define( 'id' => $faker->unique()->numberBetween(1000, 10000), 'name' => $faker->words(3, true), 'account_type_id' => 1, + 'active' => true, ]; } ); diff --git a/tests/Feature/Controllers/ExportControllerTest.php b/tests/Feature/Controllers/ExportControllerTest.php index 9f66e44c9c..6de839c908 100644 --- a/tests/Feature/Controllers/ExportControllerTest.php +++ b/tests/Feature/Controllers/ExportControllerTest.php @@ -143,12 +143,15 @@ class ExportControllerTest extends TestCase $processor->shouldReceive('collectOldUploads')->once(); $processor->shouldReceive('collectAttachments')->once(); + $job = new ExportJob; + $job->user = $this->user(); + $repository->shouldReceive('changeStatus')->andReturn(true); - $repository->shouldReceive('findByKey')->andReturn(new ExportJob); + $repository->shouldReceive('findByKey')->andReturn($job); $this->be($this->user()); - $response = $this->post(route('export.export'), $data); + $response = $this->post(route('export.submit'), $data); $response->assertStatus(200); $response->assertSee('ok'); } diff --git a/tests/Feature/Controllers/Json/AutoCompleteControllerTest.php b/tests/Feature/Controllers/Json/AutoCompleteControllerTest.php new file mode 100644 index 0000000000..e00cfb2c13 --- /dev/null +++ b/tests/Feature/Controllers/Json/AutoCompleteControllerTest.php @@ -0,0 +1,87 @@ +mock(AccountRepositoryInterface::class); + $accountRepos->shouldReceive('getAccountsByType') + ->withArgs([[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]]) + ->andReturn(new Collection); + + $this->be($this->user()); + $response = $this->get(route('json.all-accounts')); + $response->assertStatus(200); + } + + /** + * @covers \FireflyIII\Http\Controllers\Json\AutoCompleteController::expenseAccounts + */ + public function testExpenseAccounts() + { + // mock stuff + $account = factory(Account::class)->make(); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); + $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::EXPENSE, AccountType::BENEFICIARY]])->once()->andReturn( + new Collection([$account]) + ); + + $this->be($this->user()); + $response = $this->get(route('json.expense-accounts')); + $response->assertStatus(200); + $response->assertExactJson([$account->name]); + } + + /** + * @covers \FireflyIII\Http\Controllers\Json\AutoCompleteController::revenueAccounts + */ + public function testRevenueAccounts() + { + // mock stuff + $account = factory(Account::class)->make(); + $accountRepos = $this->mock(AccountRepositoryInterface::class); + $journalRepos = $this->mock(JournalRepositoryInterface::class); + $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); + $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::REVENUE]])->once()->andReturn( + new Collection([$account]) + ); + + $this->be($this->user()); + $response = $this->get(route('json.revenue-accounts')); + $response->assertStatus(200); + $response->assertExactJson([$account->name]); + } + +} \ No newline at end of file diff --git a/tests/Feature/Controllers/JsonControllerTest.php b/tests/Feature/Controllers/JsonControllerTest.php index 2afd48ca56..bef5b28839 100644 --- a/tests/Feature/Controllers/JsonControllerTest.php +++ b/tests/Feature/Controllers/JsonControllerTest.php @@ -54,21 +54,6 @@ class JsonControllerTest extends TestCase $response->assertStatus(200); } - /** - * @covers \FireflyIII\Http\Controllers\JsonController::allAccounts - */ - public function testAllAccounts() - { - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $accountRepos->shouldReceive('getAccountsByType') - ->withArgs([[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]]) - ->andReturn(new Collection); - - $this->be($this->user()); - $response = $this->get(route('json.all-accounts')); - $response->assertStatus(200); - } - /** * @covers \FireflyIII\Http\Controllers\JsonController::allTransactionJournals() */ @@ -210,45 +195,6 @@ class JsonControllerTest extends TestCase $response->assertExactJson([$category->name]); } - /** - * @covers \FireflyIII\Http\Controllers\JsonController::expenseAccounts - */ - public function testExpenseAccounts() - { - // mock stuff - $account = factory(Category::class)->make(); - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); - $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::EXPENSE, AccountType::BENEFICIARY]])->once()->andReturn( - new Collection([$account]) - ); - - $this->be($this->user()); - $response = $this->get(route('json.expense-accounts')); - $response->assertStatus(200); - $response->assertExactJson([$account->name]); - } - - /** - * @covers \FireflyIII\Http\Controllers\JsonController::revenueAccounts - */ - public function testRevenueAccounts() - { - // mock stuff - $account = factory(Category::class)->make(); - $accountRepos = $this->mock(AccountRepositoryInterface::class); - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $journalRepos->shouldReceive('first')->once()->andReturn(new TransactionJournal); - $accountRepos->shouldReceive('getAccountsByType')->withArgs([[AccountType::REVENUE]])->once()->andReturn( - new Collection([$account]) - ); - - $this->be($this->user()); - $response = $this->get(route('json.revenue-accounts')); - $response->assertStatus(200); - $response->assertExactJson([$account->name]); - } /** * @covers \FireflyIII\Http\Controllers\JsonController::tags From 3e64028e295391cc712d9bfe21a4b2c3edc3946e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 15:38:23 +0200 Subject: [PATCH 048/668] Reinstate export javascript --- resources/views/export/index.twig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/export/index.twig b/resources/views/export/index.twig index 2f7aaba776..6279fef14b 100644 --- a/resources/views/export/index.twig +++ b/resources/views/export/index.twig @@ -104,7 +104,7 @@ - + {% endblock %} {% block styles %} From 4b46a3d29840a248e64ad4418eefd2f7e5d1cca3 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 15:38:33 +0200 Subject: [PATCH 049/668] Update composer.lock file. --- composer.lock | 52 ++++++++++++++++++++++++++------------------------- 1 file changed, 27 insertions(+), 25 deletions(-) diff --git a/composer.lock b/composer.lock index a405cfb5a7..55445326ed 100644 --- a/composer.lock +++ b/composer.lock @@ -507,33 +507,33 @@ }, { "name": "doctrine/inflector", - "version": "v1.1.0", + "version": "v1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae" + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/90b2128806bfde671b6952ab8bea493942c1fdae", - "reference": "90b2128806bfde671b6952ab8bea493942c1fdae", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/e11d84c6e018beedd929cff5220969a3c6d1d462", + "reference": "e11d84c6e018beedd929cff5220969a3c6d1d462", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "4.*" + "phpunit/phpunit": "^6.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.1.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Inflector\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Inflector\\": "lib/Doctrine/Common/Inflector" } }, "notification-url": "https://packagist.org/downloads/", @@ -570,7 +570,7 @@ "singularize", "string" ], - "time": "2015-11-06T14:35:42+00:00" + "time": "2017-07-22T12:18:28+00:00" }, { "name": "doctrine/lexer", @@ -670,20 +670,20 @@ }, { "name": "laravel/framework", - "version": "v5.4.32", + "version": "v5.4.33", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "b8300578d159199b1195413b67318c79068cd24d" + "reference": "e53a81a2bf406f501cdf818ad949f8d6c8dabfc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/b8300578d159199b1195413b67318c79068cd24d", - "reference": "b8300578d159199b1195413b67318c79068cd24d", + "url": "https://api.github.com/repos/laravel/framework/zipball/e53a81a2bf406f501cdf818ad949f8d6c8dabfc0", + "reference": "e53a81a2bf406f501cdf818ad949f8d6c8dabfc0", "shasum": "" }, "require": { - "doctrine/inflector": "~1.1.0", + "doctrine/inflector": "~1.1", "erusev/parsedown": "~1.6", "ext-mbstring": "*", "ext-openssl": "*", @@ -795,7 +795,7 @@ "framework", "laravel" ], - "time": "2017-08-03T12:59:42+00:00" + "time": "2017-08-14T20:17:41+00:00" }, { "name": "laravelcollective/html", @@ -2929,29 +2929,31 @@ }, { "name": "fzaninotto/faker", - "version": "v1.6.0", + "version": "v1.7.1", "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123" + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/44f9a286a04b80c76a4e5fb7aad8bb539b920123", - "reference": "44f9a286a04b80c76a4e5fb7aad8bb539b920123", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", + "reference": "d3ed4cc37051c1ca52d22d76b437d14809fc7e0d", "shasum": "" }, "require": { - "php": "^5.3.3|^7.0" + "php": "^5.3.3 || ^7.0" }, "require-dev": { "ext-intl": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~1.5" + "phpunit/phpunit": "^4.0 || ^5.0", + "squizlabs/php_codesniffer": "^1.5" }, "type": "library", "extra": { - "branch-alias": [] + "branch-alias": { + "dev-master": "1.8-dev" + } }, "autoload": { "psr-4": { @@ -2973,7 +2975,7 @@ "faker", "fixtures" ], - "time": "2016-04-29T12:21:54+00:00" + "time": "2017-08-15T16:48:10+00:00" }, { "name": "guzzle/guzzle", From 93068659e5ae624c3263344aaa5429d371aa74a4 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 17:08:30 +0200 Subject: [PATCH 050/668] Bunq code. --- app/Services/Bunq/Id/BunqId.php | 43 ++ app/Services/Bunq/Id/DeviceServerId.php | 25 ++ app/Services/Bunq/Id/DeviceSessionId.php | 24 ++ app/Services/Bunq/Id/InstallationId.php | 24 ++ app/Services/Bunq/Object/BunqObject.php | 23 ++ app/Services/Bunq/Object/ServerPublicKey.php | 51 +++ app/Services/Bunq/Object/UserPerson.php | 134 +++++++ app/Services/Bunq/Request/BunqRequest.php | 374 ++++++++++++++++++ .../Bunq/Request/InstallationTokenRequest.php | 149 +++++++ app/Services/Bunq/Token/BunqToken.php | 89 +++++ app/Services/Bunq/Token/InstallationToken.php | 24 ++ app/Services/Bunq/Token/SessionToken.php | 25 ++ 12 files changed, 985 insertions(+) create mode 100644 app/Services/Bunq/Id/BunqId.php create mode 100644 app/Services/Bunq/Id/DeviceServerId.php create mode 100644 app/Services/Bunq/Id/DeviceSessionId.php create mode 100644 app/Services/Bunq/Id/InstallationId.php create mode 100644 app/Services/Bunq/Object/BunqObject.php create mode 100644 app/Services/Bunq/Object/ServerPublicKey.php create mode 100644 app/Services/Bunq/Object/UserPerson.php create mode 100644 app/Services/Bunq/Request/BunqRequest.php create mode 100644 app/Services/Bunq/Request/InstallationTokenRequest.php create mode 100644 app/Services/Bunq/Token/BunqToken.php create mode 100644 app/Services/Bunq/Token/InstallationToken.php create mode 100644 app/Services/Bunq/Token/SessionToken.php diff --git a/app/Services/Bunq/Id/BunqId.php b/app/Services/Bunq/Id/BunqId.php new file mode 100644 index 0000000000..9752d71ba3 --- /dev/null +++ b/app/Services/Bunq/Id/BunqId.php @@ -0,0 +1,43 @@ +id; + } + + /** + * @param int $id + */ + public function setId(int $id) + { + $this->id = $id; + } + + +} \ No newline at end of file diff --git a/app/Services/Bunq/Id/DeviceServerId.php b/app/Services/Bunq/Id/DeviceServerId.php new file mode 100644 index 0000000000..b8b845fd7e --- /dev/null +++ b/app/Services/Bunq/Id/DeviceServerId.php @@ -0,0 +1,25 @@ +publicKey = $response['server_public_key']; + } + + /** + * @param string $publicKey + */ + public function setPublicKey(string $publicKey) + { + $this->publicKey = $publicKey; + } + + /** + * @return string + */ + public function getPublicKey(): string + { + return $this->publicKey; + } + + +} \ No newline at end of file diff --git a/app/Services/Bunq/Object/UserPerson.php b/app/Services/Bunq/Object/UserPerson.php new file mode 100644 index 0000000000..963357ab51 --- /dev/null +++ b/app/Services/Bunq/Object/UserPerson.php @@ -0,0 +1,134 @@ +id = intval($data['id']); + $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']); + $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']); + $this->status = $data['status']; + $this->subStatus = $data['sub_status']; + $this->publicUuid = $data['public_uuid']; + $this->displayName = $data['display_name']; + $this->publicNickName = $data['public_nick_name']; + $this->language = $data['language']; + $this->region = $data['region']; + $this->sessionTimeout = intval($data['session_timeout']); + $this->firstName = $data['first_name']; + $this->middleName = $data['middle_name']; + $this->lastName = $data['last_name']; + $this->legalName = $data['legal_name']; + $this->taxResident = $data['tax_resident']; + $this->dateOfBirth = Carbon::createFromFormat('Y-m-d', $data['date_of_birth']); + $this->placeOfBirth = $data['place_of_birth']; + $this->countryOfBirth = $data['country_of_birth']; + $this->nationality = $data['nationality']; + $this->gender = $data['gender']; + $this->versionTos = intval($data['version_terms_of_service']); + $this->documentNumber = $data['document_number']; + $this->documentType = $data['document_type']; + $this->documentCountry = $data['document_country_of_issuance']; + + // create aliases + // create avatar + // create daily limit + // create notification filters + // create address main, postal + // document front, back attachment + // customer, customer_limit + // billing contracts + + // echo '
';
+        //        print_r($data);
+        //        var_dump($this);
+        //        echo '
'; + } + +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/BunqRequest.php b/app/Services/Bunq/Request/BunqRequest.php new file mode 100644 index 0000000000..4226204f0e --- /dev/null +++ b/app/Services/Bunq/Request/BunqRequest.php @@ -0,0 +1,374 @@ + 'X-Bunq-Client-Response-Id', + 'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id', + ]; + + public function __construct() + { + + + // create a log channel + $this->logger = new Logger('bunq-request'); + $this->logger->pushHandler(new StreamHandler('logs/bunq.log', Logger::DEBUG)); + $this->logger->debug('Hallo dan'); + } + + /** + * + */ + abstract public function call(): void; + + /** + * @return string + */ + public function getServer(): string + { + return $this->server; + } + + /** + * @param string $server + */ + public function setServer(string $server) + { + $this->server = $server; + } + + /** + * @param bool $fake + */ + public function setFake(bool $fake) + { + $this->fake = $fake; + } + + /** + * @param string $privateKey + */ + public function setPrivateKey(string $privateKey) + { + $this->privateKey = $privateKey; + } + + /** + * @param string $secret + */ + public function setSecret(string $secret) + { + $this->secret = $secret; + } + + /** + * @param ServerPublicKey $serverPublicKey + */ + public function setServerPublicKey(ServerPublicKey $serverPublicKey) + { + $this->serverPublicKey = $serverPublicKey; + } + + /** + * @param string $method + * @param string $uri + * @param array $headers + * @param string $data + * + * @return string + */ + protected function generateSignature(string $method, string $uri, array $headers, string $data): string + { + if (strlen($this->privateKey) === 0) { + throw new Exception('No private key present.'); + } + if (strtolower($method) === 'get') { + $data = ''; + } + + $uri = str_replace(['https://api.bunq.com', 'https://sandbox.public.api.bunq.com'], '', $uri); + $toSign = strtoupper($method) . ' ' . $uri . "\n"; + $headersToSign = ['Cache-Control', 'User-Agent']; + ksort($headers); + foreach ($headers as $name => $value) { + if (in_array($name, $headersToSign) || substr($name, 0, 7) === 'X-Bunq-') { + $toSign .= $name . ': ' . $value . "\n"; + } + } + $toSign .= "\n" . $data; + $signature = ''; + + openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA256); + $signature = base64_encode($signature); + + return $signature; + } + + protected function getDefaultHeaders(): array + { + return [ + 'X-Bunq-Client-Request-Id' => uniqid('sander'), + 'Cache-Control' => 'no-cache', + 'User-Agent' => 'pre-Firefly III test thing', + 'X-Bunq-Language' => 'en_US', + 'X-Bunq-Region' => 'nl_NL', + 'X-Bunq-Geolocation' => '0 0 0 0 NL', + ]; + } + + /** + * @param string $key + * @param array $response + * + * @return array + */ + protected function getKeyFromResponse(string $key, array $response): array + { + if (isset($response['Response'])) { + foreach ($response['Response'] as $entry) { + $currentKey = key($entry); + $data = current($entry); + if ($currentKey === $key) { + return $data; + } + } + } + + return []; + } + + /** + * @param string $uri + * @param array $data + * @param array $headers + * + * @return array + * @throws Exception + */ + protected function sendSignedBunqGet(string $uri, array $data, array $headers): array + { + if (strlen($this->server) === 0) { + throw new Exception('No bunq server defined'); + } + + $body = json_encode($data); + $fullUri = $this->server . $uri; + $signature = $this->generateSignature('get', $uri, $headers, $body); + $headers['X-Bunq-Client-Signature'] = $signature; + try { + $response = Requests::get($fullUri, $headers); + } catch (Requests_Exception $e) { + return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; + } + + $body = $response->body; + $array = json_decode($body, true); + if ($this->isErrorResponse($array)) { + $this->throwResponseError($array); + } + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { + throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri)); + } + $array['ResponseHeaders'] = $responseHeaders; + + return $array; + } + + /** + * @param string $uri + * @param array $data + * @param array $headers + * + * @return array + * @throws Exception + */ + protected function sendSignedBunqPost(string $uri, array $data, array $headers): array + { + $body = json_encode($data); + $fullUri = $this->server . $uri; + $signature = $this->generateSignature('post', $uri, $headers, $body); + $headers['X-Bunq-Client-Signature'] = $signature; + try { + $response = Requests::post($fullUri, $headers, $body); + } catch (Requests_Exception $e) { + return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; + } + + $body = $response->body; + $array = json_decode($body, true); + if ($this->isErrorResponse($array)) { + $this->throwResponseError($array); + } + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { + throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri)); + } + $array['ResponseHeaders'] = $responseHeaders; + + return $array; + } + + /** + * @param string $uri + * @param array $data + * @param array $headers + * + * @return array + */ + protected function sendUnsignedBunqPost(string $uri, array $data, array $headers): array + { + $body = json_encode($data); + $fullUri = $this->server . $uri; + try { + $response = Requests::post($fullUri, $headers, $body); + } catch (Requests_Exception $e) { + return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; + } + $body = $response->body; + $responseHeaders = $response->headers->getAll(); + $array = json_decode($body, true); + if ($this->isErrorResponse($array)) { + $this->throwResponseError($array); + } + $array['ResponseHeaders'] = $responseHeaders; + + return $array; + } + + /** + * @param array $response + * + * @return bool + */ + private function isErrorResponse(array $response): bool + { + $key = key($response); + if ($key === 'Error') { + return true; + } + + return false; + } + + /** + * @param array $response + * + * @throws Exception + */ + private function throwResponseError(array $response) + { + echo '
' . print_r($response, true) . '

'; + $message = []; + if (isset($response['Error'])) { + foreach ($response['Error'] as $error) { + $message[] = $error['error_description']; + } + } + throw new Exception(join(', ', $message)); + } + + /** + * @param string $body + * @param array $headers + * @param int $statusCode + * + * @return bool + * @throws Exception + */ + private function verifyServerSignature(string $body, array $headers, int $statusCode): bool + { + $this->logger->debug('Going to verify signature for body+headers+status'); + $dataToVerify = $statusCode . "\n"; + $verifyHeaders = []; + + // false when no public key is present + if (is_null($this->serverPublicKey)) { + $this->logger->error('No public key present in class, so return FALSE.'); + + return false; + } + //$this->logger->debug('Given headers', $headers); + foreach ($headers as $header => $value) { + + // skip non-bunq headers or signature + if (substr($header, 0, 7) !== 'x-bunq-' || $header === 'x-bunq-server-signature') { + continue; + } + // need to have upper case variant of header: + if (!isset($this->upperCaseHeaders[$header])) { + throw new Exception(sprintf('No upper case variant for header "%s"', $header)); + } + $header = $this->upperCaseHeaders[$header]; + $verifyHeaders[$header] = $value[0]; + } + // sort verification headers: + ksort($verifyHeaders); + + //$this->logger->debug('Final headers for verification', $verifyHeaders); + + // add them to data to sign: + foreach ($verifyHeaders as $header => $value) { + $dataToVerify .= $header . ': ' . trim($value) . "\n"; + } + + $signature = $headers['x-bunq-server-signature'][0]; + $dataToVerify .= "\n" . $body; + + //$this->logger->debug(sprintf('Signature to verify: "%s"', $signature)); + + $result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256); + + if (is_int($result) && $result < 1) { + $this->logger->error(sprintf('Result of verification is %d, return false.', $result)); + + return false; + } + if (!is_int($result)) { + $this->logger->error(sprintf('Result of verification is a boolean (%d), return false.', $result)); + } + $this->logger->info('Signature is a match, return true.'); + + return true; + } +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/InstallationTokenRequest.php b/app/Services/Bunq/Request/InstallationTokenRequest.php new file mode 100644 index 0000000000..d23e573a18 --- /dev/null +++ b/app/Services/Bunq/Request/InstallationTokenRequest.php @@ -0,0 +1,149 @@ + $this->publicKey,]; + $headers = $this->getDefaultHeaders(); + $response = []; + if ($this->fake) { + $response = json_decode( + '{"Response":[{"Id":{"id":875936}},{"Token":{"id":13172597,"created":"2017-08-05 11:46:07.061740","updated":"2017-08-05 11:46:07.061740","token":"35278fcc8b0615261fe23285e6d2e6ccd05ac4c93454981bd5e985ec453e5b5d"}},{"ServerPublicKey":{"server_public_key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAon5y6OZGvTN8kIqPBdro\ndG8TWVw6sl34hAWI47NK6Pi7gmnTtd\/k9gfwq56iI4Er8uMM5e4QmjD++XrBIqcw\nHohDVK03li3xsyJPZ4EBSUOkv4VKXKL\/quqlSgDmPnxtT39BowUZl1um5QbTm0hW\npGI\/0bK7jQk7mbEan9yDOpXnczKgfNlo4o+zbFquPdUfA5LE8R8X057dB6ab7eqA\n9Aybo+I6xyrsOOztufg3Yfe5RA6a0Sikqe\/L8HCP+9TJByUI2pwydPou3KONfYhK\n1NQJZ+RCZ6V+jmcuzKe2vq0jhBZd26wNscl48Sm7etJeuBOpHE+MgO24JiTEYlLS\nVQIDAQAB\n-----END PUBLIC KEY-----\n"}}]}', + true + ); + } + if (!$this->fake) { + $response = $this->sendUnsignedBunqPost($uri, $data, $headers); + } + //echo '
' . json_encode($response) . '

'; + + $this->installationId = $this->extractInstallationId($response); + $this->serverPublicKey = $this->extractServerPublicKey($response); + $this->installationToken = $this->extractInstallationToken($response); + + return; + } + + /** + * @return InstallationId + */ + public function getInstallationId(): InstallationId + { + return $this->installationId; + } + + /** + * @return InstallationToken + */ + public function getInstallationToken(): InstallationToken + { + return $this->installationToken; + } + + /** + * @return string + */ + public function getPublicKey(): string + { + return $this->publicKey; + } + + /** + * @param string $publicKey + */ + public function setPublicKey(string $publicKey) + { + $this->publicKey = $publicKey; + } + + /** + * @return ServerPublicKey + */ + public function getServerPublicKey(): ServerPublicKey + { + return $this->serverPublicKey; + } + + /** + * @param bool $fake + */ + public function setFake(bool $fake) + { + $this->fake = $fake; + } + + /** + * @param array $response + * + * @return InstallationId + */ + private function extractInstallationId(array $response): InstallationId + { + $installationId = new InstallationId; + $data = $this->getKeyFromResponse('Id', $response); + $installationId->setId(intval($data['id'])); + + return $installationId; + + } + + /** + * @param array $response + * + * @return InstallationToken + */ + private function extractInstallationToken(array $response): InstallationToken + { + + $data = $this->getKeyFromResponse('Token', $response); + $installationToken = new InstallationToken($data); + + return $installationToken; + } + + /** + * @param array $response + * + * @return ServerPublicKey + */ + private function extractServerPublicKey(array $response): ServerPublicKey + { + $data = $this->getKeyFromResponse('ServerPublicKey', $response); + $serverPublicKey = new ServerPublicKey($data); + + return $serverPublicKey; + } +} \ No newline at end of file diff --git a/app/Services/Bunq/Token/BunqToken.php b/app/Services/Bunq/Token/BunqToken.php new file mode 100644 index 0000000000..7cb9ba486d --- /dev/null +++ b/app/Services/Bunq/Token/BunqToken.php @@ -0,0 +1,89 @@ +makeTokenFromResponse($response); + } + + /** + * @return Carbon + */ + public function getCreated(): Carbon + { + return $this->created; + } + + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + /** + * @return string + */ + public function getToken(): string + { + return $this->token; + } + + /** + * @return Carbon + */ + public function getUpdated(): Carbon + { + return $this->updated; + } + + /** + * @param array $response + */ + protected function makeTokenFromResponse(array $response): void + { + $this->id = $response['id']; + $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $response['created']); + $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $response['updated']); + $this->token = $response['token']; + + return; + } + +} \ No newline at end of file diff --git a/app/Services/Bunq/Token/InstallationToken.php b/app/Services/Bunq/Token/InstallationToken.php new file mode 100644 index 0000000000..59777b0f3a --- /dev/null +++ b/app/Services/Bunq/Token/InstallationToken.php @@ -0,0 +1,24 @@ + Date: Fri, 18 Aug 2017 17:30:06 +0200 Subject: [PATCH 051/668] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index cf55f8a024..150c44d8d7 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -57,9 +57,9 @@ return [ 'json' => 'El campo :attribute debe ser una cadena JSON válida.', 'max.numeric' => 'El campo :attribute no puede ser mayor que :max.', 'max.file' => 'El campo :attribute no puede ser mayor :max de kilobytes.', - 'max.string' => 'The :attribute may not be greater than :max characters.', - 'max.array' => 'The :attribute may not have more than :max items.', - 'mimes' => 'The :attribute must be a file of type: :values.', + 'max.string' => 'El campo :attribute debe contener menos de :max caracteres.', + 'max.array' => 'El campo :attribute debe contener al menos :max elementos.', + 'mimes' => 'El campo :attribute debe ser un archivo de tipo :values.', 'min.numeric' => 'The :attribute must be at least :min.', 'min.file' => 'The :attribute must be at least :min kilobytes.', 'min.string' => 'The :attribute must be at least :min characters.', From 4c3dbc6debae674ff033d4f92b826479139119bc Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 17:40:06 +0200 Subject: [PATCH 052/668] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 62 ++++++++++++++--------------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 150c44d8d7..062c56feaf 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -60,35 +60,35 @@ return [ 'max.string' => 'El campo :attribute debe contener menos de :max caracteres.', 'max.array' => 'El campo :attribute debe contener al menos :max elementos.', 'mimes' => 'El campo :attribute debe ser un archivo de tipo :values.', - 'min.numeric' => 'The :attribute must be at least :min.', - 'min.file' => 'The :attribute must be at least :min kilobytes.', - 'min.string' => 'The :attribute must be at least :min characters.', - 'min.array' => 'The :attribute must have at least :min items.', - 'not_in' => 'The selected :attribute is invalid.', - 'numeric' => 'The :attribute must be a number.', - 'regex' => 'The :attribute format is invalid.', - 'required' => 'The :attribute field is required.', - 'required_if' => 'The :attribute field is required when :other is :value.', - 'required_unless' => 'The :attribute field is required unless :other is in :values.', - 'required_with' => 'The :attribute field is required when :values is present.', - 'required_with_all' => 'The :attribute field is required when :values is present.', - 'required_without' => 'The :attribute field is required when :values is not present.', - 'required_without_all' => 'The :attribute field is required when none of :values are present.', - 'same' => 'The :attribute and :other must match.', - 'size.numeric' => 'The :attribute must be :size.', - 'size.file' => 'The :attribute must be :size kilobytes.', - 'size.string' => 'The :attribute must be :size characters.', - 'size.array' => 'The :attribute must contain :size items.', - 'unique' => 'The :attribute has already been taken.', - 'string' => 'The :attribute must be a string.', - 'url' => 'The :attribute format is invalid.', - 'timezone' => 'The :attribute must be a valid zone.', - '2fa_code' => 'The :attribute field is invalid.', - 'dimensions' => 'The :attribute has invalid image dimensions.', - 'distinct' => 'The :attribute field has a duplicate value.', - 'file' => 'The :attribute must be a file.', - 'in_array' => 'The :attribute field does not exist in :other.', - 'present' => 'The :attribute field must be present.', - 'amount_zero' => 'The total amount cannot be zero', - 'secure_password' => 'This is not a secure password. Please try again. For more information, visit https://goo.gl/NCh2tN', + 'min.numeric' => 'El campo :attribute debe ser al menos :min.', + 'min.file' => 'El campo :attribute debe ser al menos :min kilobytes.', + 'min.string' => 'El campo :attribute debe contener al menos :min caracteres.', + 'min.array' => 'El campo :attribute debe tener al menos :min elementos.', + 'not_in' => 'El campo :attribute seleccionado es inválido.', + 'numeric' => 'El campo :attribute debe ser un número.', + 'regex' => 'El formato del campo :attribute no es válido.', + 'required' => 'El campo :attribute es obligatorio.', + 'required_if' => 'El campo :attribute es obligatorio cuando el campo :other es :value.', + 'required_unless' => 'El campo :attribute es obligatorio a menos que :other se encuentre en :values.', + 'required_with' => 'El campo :attribute es obligatorio cuando :values está presente.', + 'required_with_all' => 'El campo :attribute es obligatorio cuando :values está presente.', + 'required_without' => 'El campo :attribute es obligatorio cuando :values no está presente.', + 'required_without_all' => 'El campo :attribute es obligatorio cuando ningún campo :values está presente.', + 'same' => 'El campo atributo :attribute y :other deben coincidir.', + 'size.numeric' => 'El tamaño de :attribute debe ser :size.', + 'size.file' => 'El tamaño de :attribute debe ser :size kilobytes.', + 'size.string' => 'El campo :attribute debe tener :size caracteres.', + 'size.array' => 'El campo :attribute debe contener :size elementos.', + 'unique' => 'El elemento :attribute ya está en uso.', + 'string' => 'El campo :atribute debe ser un texto.', + 'url' => 'El formato del campo :attribute no es válido.', + 'timezone' => 'El campo :attribute debe contener una zona válida.', + '2fa_code' => 'El campo :attribute no es válido.', + 'dimensions' => 'Las dimensiones de la imagen :attribute son incorrectas.', + 'distinct' => 'El campo :attribute tiene un valor duplicado.', + 'file' => 'El campo :attribute debe ser un fichero.', + 'in_array' => 'El campo :attribute no existe en :other.', + 'present' => 'El campo :attribute debe estar presente.', + 'amount_zero' => 'La cantidad total no puede ser cero', + 'secure_password' => 'Esta contraseña no es segura. Por favor inténtalo de nuevo. Para más información, visita https://goo.gl/NCh2tN', ]; \ No newline at end of file From 4b5d363f5521708eb25dfe2bb26704683fc0a9e1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 17:50:06 +0200 Subject: [PATCH 053/668] New translations validation.php (Spanish) --- resources/lang/es_ES/validation.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 062c56feaf..cb5c33ed45 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -12,16 +12,16 @@ declare(strict_types=1); */ return [ - 'iban' => 'This is not a valid CBU.', + 'iban' => 'Este no es un IBAN válido.', 'unique_account_number_for_user' => 'Parece que este número de cuenta ya está en uso.', 'deleted_user' => 'Debido a restricciones de seguridad, no se puede registrar utilizando esta dirección de correo electrónico.', - 'rule_trigger_value' => 'Este valor es válido para el disparador seleccionado.', - 'rule_action_value' => 'Este valor es inválido para la acción seleccionado.', + 'rule_trigger_value' => 'Este valor es incorrecto para el disparador seleccionado.', + 'rule_action_value' => 'Este valor es incorrecto para la acción seleccionada.', 'invalid_domain' => 'Debido a restricciones de seguridad, no se puede registrar utilizando este dominio.', - 'file_already_attached' => 'Archivo ":name" ya ha sido añadido para este objeto.', + 'file_already_attached' => 'El archivo ":name" ya ha sido añadido a este objeto.', 'file_attached' => 'Archivo subido correctamente ": nombre".', - 'file_invalid_mime' => 'Archivo ":name" es de tipo": mime" que no es aceptado como una nueva carga.', - 'file_too_large' => 'Archivo ":name" es demasiado grande.', + 'file_invalid_mime' => 'El archivo ":name" es de tipo ": mime", el cual no se acepta.', + 'file_too_large' => 'El archivo ":name" es demasiado grande.', 'belongs_to_user' => 'El valor de :attribute es desconocido', 'accepted' => 'El :attribute debe ser aceptado.', 'bic' => 'Esto no es un BIC válido.', @@ -64,7 +64,7 @@ return [ 'min.file' => 'El campo :attribute debe ser al menos :min kilobytes.', 'min.string' => 'El campo :attribute debe contener al menos :min caracteres.', 'min.array' => 'El campo :attribute debe tener al menos :min elementos.', - 'not_in' => 'El campo :attribute seleccionado es inválido.', + 'not_in' => 'El campo :attribute seleccionado es incorrecto.', 'numeric' => 'El campo :attribute debe ser un número.', 'regex' => 'El formato del campo :attribute no es válido.', 'required' => 'El campo :attribute es obligatorio.', From 33e381b5da3c00c7abef8c697413e10d761a2bec Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 17:50:07 +0200 Subject: [PATCH 054/668] New translations form.php (Spanish) --- resources/lang/es_ES/form.php | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index 743ff7f1fc..47b109fdc0 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -18,7 +18,7 @@ return [ 'bank_balance' => 'Saldo', 'savings_balance' => 'Salgo de ahorro', 'credit_card_limit' => 'Límite de la tarjeta de crédito', - 'automatch' => 'Match automatically', + 'automatch' => 'Coinciden automáticamente', 'skip' => 'Saltar', 'name' => 'Nombre', 'active' => 'Activo', @@ -78,14 +78,14 @@ return [ 'destination_account_expense' => 'Destination account (expense account)', 'destination_account_asset' => 'Destination account (asset account)', 'source_account_revenue' => 'Source account (revenue account)', - 'type' => 'Type', + 'type' => 'Tipo', 'convert_Withdrawal' => 'Convert withdrawal', 'convert_Deposit' => 'Convert deposit', 'convert_Transfer' => 'Convert transfer', - 'amount' => 'Amount', - 'date' => 'Date', + 'amount' => 'Importe', + 'date' => 'Fecha', 'interest_date' => 'Interest date', 'book_date' => 'Book date', 'process_date' => 'Processing date', @@ -94,16 +94,16 @@ return [ 'deletePermanently' => 'Delete permanently', 'cancel' => 'Cancel', 'targetdate' => 'Target date', - 'tag' => 'Tag', + 'tag' => 'Etiqueta', 'under' => 'Under', - 'symbol' => 'Symbol', - 'code' => 'Code', + 'symbol' => 'Símbolo', + 'code' => 'Código', 'iban' => 'IBAN', - 'accountNumber' => 'Account number', - 'has_headers' => 'Headers', - 'date_format' => 'Date format', + 'accountNumber' => 'Número de cuenta', + 'has_headers' => 'Encabezados', + 'date_format' => 'Formato de fecha', 'specifix' => 'Bank- or file specific fixes', - 'attachments[]' => 'Attachments', + 'attachments[]' => 'Adjuntos', 'store_new_withdrawal' => 'Store new withdrawal', 'store_new_deposit' => 'Store new deposit', 'store_new_transfer' => 'Store new transfer', @@ -111,12 +111,12 @@ return [ 'add_new_deposit' => 'Add a new deposit', 'add_new_transfer' => 'Add a new transfer', 'noPiggybank' => '(no piggy bank)', - 'title' => 'Title', - 'notes' => 'Notes', - 'filename' => 'File name', - 'mime' => 'Mime type', - 'size' => 'Size', - 'trigger' => 'Trigger', + 'title' => 'Título', + 'notes' => 'Notas', + 'filename' => 'Nombre de fichero', + 'mime' => 'Tipo Mime', + 'size' => 'Tamaño', + 'trigger' => 'Disparador', 'stop_processing' => 'Stop processing', 'start_date' => 'Start of range', 'end_date' => 'End of range', From df443aa34c72be141f1f245d193ce3546e68a22e Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 21:08:51 +0200 Subject: [PATCH 055/668] Add copyright things. --- app/Console/Commands/UseEncryption.php | 10 ++++++++++ app/Mail/RegisteredUser.php | 10 ++++++++++ app/Mail/RequestedNewPassword.php | 10 ++++++++++ config/auth.php | 6 +++--- config/broadcasting.php | 7 ++++--- config/cache.php | 8 +++++--- config/compile.php | 6 +++--- config/database.php | 7 ++++--- config/filesystems.php | 7 ++++--- config/firefly.php | 3 +++ config/mail.php | 7 ++++--- config/queue.php | 7 ++++--- config/services.php | 7 ++++--- config/twigbridge.php | 15 ++++++++------- ...016_10_09_150037_expand_transactions_table.php | 6 +++--- .../2016_10_22_075804_changes_for_v410.php | 6 +++--- .../2016_11_24_210552_changes_for_v420.php | 6 +++--- .../2016_12_22_150431_changes_for_v430.php | 10 ++++++++++ .../2016_12_28_203205_changes_for_v431.php | 8 +++++--- .../2017_04_13_163623_changes_for_v440.php | 10 ++++++++++ .../2017_06_02_105232_changes_for_v450.php | 10 ++++++++++ gulpfile.js | 9 +++++++++ phpunit.coverage.xml | 9 +++++++++ phpunit.xml | 9 +++++++++ tests/CreatesApplication.php | 5 ++--- tests/Feature/ExampleTest.php | 6 ++++-- tests/TestCase.php | 3 ++- tests/Unit/ExampleTest.php | 10 ++++++++++ 28 files changed, 165 insertions(+), 52 deletions(-) diff --git a/app/Console/Commands/UseEncryption.php b/app/Console/Commands/UseEncryption.php index 5cf80aff32..ef0e68ac4a 100644 --- a/app/Console/Commands/UseEncryption.php +++ b/app/Console/Commands/UseEncryption.php @@ -1,4 +1,14 @@ [ 'guard' => 'web', diff --git a/config/broadcasting.php b/config/broadcasting.php index e4012544ff..3257e542d3 100644 --- a/config/broadcasting.php +++ b/config/broadcasting.php @@ -1,16 +1,17 @@ [ 'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites', ], + 'bunq' => [ + 'server' => 'https://sandbox.public.api.bunq.com', + ], 'default_export_format' => 'csv', 'default_import_format' => 'csv', 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], diff --git a/config/mail.php b/config/mail.php index 61d69cdc5b..f5d33fd68e 100644 --- a/config/mail.php +++ b/config/mail.php @@ -1,16 +1,17 @@ - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. + * See the LICENSE file for details. */ +declare(strict_types=1); + + /** * Configuration options for Twig. */ diff --git a/database/migrations/2016_10_09_150037_expand_transactions_table.php b/database/migrations/2016_10_09_150037_expand_transactions_table.php index 6b4e345582..6909aaa2a5 100644 --- a/database/migrations/2016_10_09_150037_expand_transactions_table.php +++ b/database/migrations/2016_10_09_150037_expand_transactions_table.php @@ -1,16 +1,16 @@ + + + + Date: Fri, 18 Aug 2017 21:09:22 +0200 Subject: [PATCH 056/668] Can now register with Bunq and get device server ID. --- .../Controllers/Import/BankController.php | 46 +++- app/Models/Preference.php | 13 +- app/Services/Bunq/Object/DeviceServer.php | 63 +++++ app/Services/Bunq/Request/BunqRequest.php | 84 +++--- .../Bunq/Request/DeviceServerRequest.php | 82 ++++++ .../Bunq/Request/InstallationTokenRequest.php | 20 +- .../Bunq/Request/ListDeviceServerRequest.php | 74 ++++++ .../Prerequisites/BunqPrerequisites.php | 241 ++++++++++++++++++ .../Prerequisites/PrerequisitesInterface.php | 9 + .../views/import/bunq/prerequisites.twig | 4 +- routes/web.php | 2 + 11 files changed, 577 insertions(+), 61 deletions(-) create mode 100644 app/Services/Bunq/Object/DeviceServer.php create mode 100644 app/Services/Bunq/Request/DeviceServerRequest.php create mode 100644 app/Services/Bunq/Request/ListDeviceServerRequest.php diff --git a/app/Http/Controllers/Import/BankController.php b/app/Http/Controllers/Import/BankController.php index d78620c9e3..17af47128c 100644 --- a/app/Http/Controllers/Import/BankController.php +++ b/app/Http/Controllers/Import/BankController.php @@ -14,17 +14,54 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface; +use Illuminate\Http\Request; +use Log; +use Session; class BankController extends Controller { - public function postPrerequisites() + public function form() { } + /** + * @param Request $request + * @param string $bank + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function postPrerequisites(Request $request, string $bank) + { + Log::debug(sprintf('Now in postPrerequisites for %s', $bank)); + $class = config(sprintf('firefly.import_pre.%s', $bank)); + /** @var PrerequisitesInterface $object */ + $object = app($class); + $object->setUser(auth()->user()); + if (!$object->hasPrerequisites()) { + //Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank)); + //return redirect(route('import.bank.form', [$bank])); + } + Log::debug('Going to store entered preprerequisites.'); + // store post data + $result = $object->storePrerequisites($request); + echo 'done with prereq'; + exit; + + if ($result->count() > 0) { + Session::flash('error', $result->first()); + + return redirect(route('import.bank.prerequisites', [$bank])); + } + + return redirect(route('import.bank.form', [$bank])); + } + /** * @param string $bank + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View */ public function prerequisites(string $bank) { @@ -33,17 +70,16 @@ class BankController extends Controller $object = app($class); $object->setUser(auth()->user()); - if ($object->hasPrerequisites()) { + //if ($object->hasPrerequisites()) { $view = $object->getView(); $parameters = $object->getViewParameters(); return view($view, $parameters); - } + //} if (!$object->hasPrerequisites()) { - echo 'redirect to import form.'; + return redirect(route('import.bank.form', [$bank])); } - } } diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 7b7922b4e0..4bacffea77 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -14,6 +14,7 @@ declare(strict_types=1); namespace FireflyIII\Models; use Crypt; +use Exception; use FireflyIII\Exceptions\FireflyException; use Illuminate\Contracts\Encryption\DecryptException; use Illuminate\Database\Eloquent\Model; @@ -56,7 +57,15 @@ class Preference extends Model sprintf('Could not decrypt preference #%d. If this error persists, please run "php artisan cache:clear" on the command line.', $this->id) ); } - + $unserialized = false; + try { + $unserialized = unserialize($data); + } catch (Exception $e) { + // don't care, assume is false. + } + if (!($unserialized === false)) { + return $unserialized; + } return json_decode($data, true); } @@ -66,7 +75,7 @@ class Preference extends Model */ public function setDataAttribute($value) { - $this->attributes['data'] = Crypt::encrypt(json_encode($value)); + $this->attributes['data'] = Crypt::encrypt(serialize($value)); } /** diff --git a/app/Services/Bunq/Object/DeviceServer.php b/app/Services/Bunq/Object/DeviceServer.php new file mode 100644 index 0000000000..35d1dd712b --- /dev/null +++ b/app/Services/Bunq/Object/DeviceServer.php @@ -0,0 +1,63 @@ +setId($data['id']); + $this->id = $id; + $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']); + $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']); + $this->ip = $data['ip']; + $this->description = $data['description']; + $this->status = $data['status']; + } + + /** + * @return DeviceServerId + */ + public function getId(): DeviceServerId + { + return $this->id; + } + + /** + * @return string + */ + public function getIp(): string + { + return $this->ip; + } + + +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/BunqRequest.php b/app/Services/Bunq/Request/BunqRequest.php index 4226204f0e..6712f178d6 100644 --- a/app/Services/Bunq/Request/BunqRequest.php +++ b/app/Services/Bunq/Request/BunqRequest.php @@ -12,10 +12,10 @@ declare(strict_types=1); namespace FireflyIII\Services\Bunq\Request; -use Bunq\Object\ServerPublicKey; use Exception; -use Monolog\Handler\StreamHandler; -use Monolog\Logger; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Services\Bunq\Object\ServerPublicKey; +use Log; use Requests; use Requests_Exception; @@ -26,12 +26,8 @@ use Requests_Exception; */ abstract class BunqRequest { - /** @var bool */ - protected $fake = false; /** @var string */ protected $secret = ''; - /** @var Logger */ - private $logger; /** @var string */ private $privateKey = ''; /** @var string */ @@ -44,14 +40,11 @@ abstract class BunqRequest 'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id', ]; + /** + * BunqRequest constructor. + */ public function __construct() { - - - // create a log channel - $this->logger = new Logger('bunq-request'); - $this->logger->pushHandler(new StreamHandler('logs/bunq.log', Logger::DEBUG)); - $this->logger->debug('Hallo dan'); } /** @@ -75,14 +68,6 @@ abstract class BunqRequest $this->server = $server; } - /** - * @param bool $fake - */ - public function setFake(bool $fake) - { - $this->fake = $fake; - } - /** * @param string $privateKey */ @@ -142,12 +127,36 @@ abstract class BunqRequest return $signature; } + /** + * @param string $key + * @param array $response + * + * @return array + */ + protected function getArrayFromResponse(string $key, array $response): array + { + $result = []; + if (isset($response['Response'])) { + foreach ($response['Response'] as $entry) { + $currentKey = key($entry); + $data = current($entry); + if ($currentKey === $key) { + $result[] = $data; + } + } + } + + return $result; + } + protected function getDefaultHeaders(): array { + $userAgent = sprintf('FireflyIII v%s', config('firefly.version')); + return [ - 'X-Bunq-Client-Request-Id' => uniqid('sander'), + 'X-Bunq-Client-Request-Id' => uniqid('FFIII'), 'Cache-Control' => 'no-cache', - 'User-Agent' => 'pre-Firefly III test thing', + 'User-Agent' => $userAgent, 'X-Bunq-Language' => 'en_US', 'X-Bunq-Region' => 'nl_NL', 'X-Bunq-Geolocation' => '0 0 0 0 NL', @@ -186,7 +195,7 @@ abstract class BunqRequest protected function sendSignedBunqGet(string $uri, array $data, array $headers): array { if (strlen($this->server) === 0) { - throw new Exception('No bunq server defined'); + throw new FireflyException('No bunq server defined'); } $body = json_encode($data); @@ -207,7 +216,7 @@ abstract class BunqRequest $responseHeaders = $response->headers->getAll(); $statusCode = $response->status_code; if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { - throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri)); + throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); } $array['ResponseHeaders'] = $responseHeaders; @@ -242,7 +251,7 @@ abstract class BunqRequest $responseHeaders = $response->headers->getAll(); $statusCode = $response->status_code; if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { - throw new Exception(sprintf('Could not verify signature for request to "%s"', $uri)); + throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); } $array['ResponseHeaders'] = $responseHeaders; @@ -298,14 +307,13 @@ abstract class BunqRequest */ private function throwResponseError(array $response) { - echo '
' . print_r($response, true) . '

'; $message = []; if (isset($response['Error'])) { foreach ($response['Error'] as $error) { $message[] = $error['error_description']; } } - throw new Exception(join(', ', $message)); + throw new FireflyException(join(', ', $message)); } /** @@ -318,17 +326,16 @@ abstract class BunqRequest */ private function verifyServerSignature(string $body, array $headers, int $statusCode): bool { - $this->logger->debug('Going to verify signature for body+headers+status'); + Log::debug('Going to verify signature for body+headers+status'); $dataToVerify = $statusCode . "\n"; $verifyHeaders = []; // false when no public key is present if (is_null($this->serverPublicKey)) { - $this->logger->error('No public key present in class, so return FALSE.'); + Log::error('No public key present in class, so return FALSE.'); return false; } - //$this->logger->debug('Given headers', $headers); foreach ($headers as $header => $value) { // skip non-bunq headers or signature @@ -337,7 +344,7 @@ abstract class BunqRequest } // need to have upper case variant of header: if (!isset($this->upperCaseHeaders[$header])) { - throw new Exception(sprintf('No upper case variant for header "%s"', $header)); + throw new FireflyException(sprintf('No upper case variant for header "%s"', $header)); } $header = $this->upperCaseHeaders[$header]; $verifyHeaders[$header] = $value[0]; @@ -345,8 +352,6 @@ abstract class BunqRequest // sort verification headers: ksort($verifyHeaders); - //$this->logger->debug('Final headers for verification', $verifyHeaders); - // add them to data to sign: foreach ($verifyHeaders as $header => $value) { $dataToVerify .= $header . ': ' . trim($value) . "\n"; @@ -354,20 +359,17 @@ abstract class BunqRequest $signature = $headers['x-bunq-server-signature'][0]; $dataToVerify .= "\n" . $body; - - //$this->logger->debug(sprintf('Signature to verify: "%s"', $signature)); - - $result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256); + $result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256); if (is_int($result) && $result < 1) { - $this->logger->error(sprintf('Result of verification is %d, return false.', $result)); + Log::error(sprintf('Result of verification is %d, return false.', $result)); return false; } if (!is_int($result)) { - $this->logger->error(sprintf('Result of verification is a boolean (%d), return false.', $result)); + Log::error(sprintf('Result of verification is a boolean (%d), return false.', $result)); } - $this->logger->info('Signature is a match, return true.'); + Log::info('Signature is a match, return true.'); return true; } diff --git a/app/Services/Bunq/Request/DeviceServerRequest.php b/app/Services/Bunq/Request/DeviceServerRequest.php new file mode 100644 index 0000000000..d997064656 --- /dev/null +++ b/app/Services/Bunq/Request/DeviceServerRequest.php @@ -0,0 +1,82 @@ + $this->description, 'secret' => $this->secret, 'permitted_ips' => $this->permittedIps]; + $headers = $this->getDefaultHeaders(); + $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); + $response = $this->sendSignedBunqPost($uri, $data, $headers); + $deviceServerId = new DeviceServerId; + $deviceServerId->setId(intval($response['Response'][0]['Id']['id'])); + $this->deviceServerId = $deviceServerId; + + return; + } + + /** + * @return DeviceServerId + */ + public function getDeviceServerId(): DeviceServerId + { + return $this->deviceServerId; + } + + /** + * @param string $description + */ + public function setDescription(string $description) + { + $this->description = $description; + } + + /** + * @param InstallationToken $installationToken + */ + public function setInstallationToken(InstallationToken $installationToken) + { + $this->installationToken = $installationToken; + } + + /** + * @param array $permittedIps + */ + public function setPermittedIps(array $permittedIps) + { + $this->permittedIps = $permittedIps; + } +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/InstallationTokenRequest.php b/app/Services/Bunq/Request/InstallationTokenRequest.php index d23e573a18..d941e09a6c 100644 --- a/app/Services/Bunq/Request/InstallationTokenRequest.php +++ b/app/Services/Bunq/Request/InstallationTokenRequest.php @@ -13,6 +13,9 @@ declare(strict_types=1); namespace FireflyIII\Services\Bunq\Request; use FireflyIII\Services\Bunq\Id\InstallationId; +use FireflyIII\Services\Bunq\Object\ServerPublicKey; +use FireflyIII\Services\Bunq\Token\InstallationToken; +use Log; /** * Class InstallationTokenRequest @@ -38,22 +41,17 @@ class InstallationTokenRequest extends BunqRequest $uri = '/v1/installation'; $data = ['client_public_key' => $this->publicKey,]; $headers = $this->getDefaultHeaders(); - $response = []; - if ($this->fake) { - $response = json_decode( - '{"Response":[{"Id":{"id":875936}},{"Token":{"id":13172597,"created":"2017-08-05 11:46:07.061740","updated":"2017-08-05 11:46:07.061740","token":"35278fcc8b0615261fe23285e6d2e6ccd05ac4c93454981bd5e985ec453e5b5d"}},{"ServerPublicKey":{"server_public_key":"-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAon5y6OZGvTN8kIqPBdro\ndG8TWVw6sl34hAWI47NK6Pi7gmnTtd\/k9gfwq56iI4Er8uMM5e4QmjD++XrBIqcw\nHohDVK03li3xsyJPZ4EBSUOkv4VKXKL\/quqlSgDmPnxtT39BowUZl1um5QbTm0hW\npGI\/0bK7jQk7mbEan9yDOpXnczKgfNlo4o+zbFquPdUfA5LE8R8X057dB6ab7eqA\n9Aybo+I6xyrsOOztufg3Yfe5RA6a0Sikqe\/L8HCP+9TJByUI2pwydPou3KONfYhK\n1NQJZ+RCZ6V+jmcuzKe2vq0jhBZd26wNscl48Sm7etJeuBOpHE+MgO24JiTEYlLS\nVQIDAQAB\n-----END PUBLIC KEY-----\n"}}]}', - true - ); - } - if (!$this->fake) { - $response = $this->sendUnsignedBunqPost($uri, $data, $headers); - } - //echo '
' . json_encode($response) . '

'; + $response = $this->sendUnsignedBunqPost($uri, $data, $headers); + Log::debug('Installation request response', $response); $this->installationId = $this->extractInstallationId($response); $this->serverPublicKey = $this->extractServerPublicKey($response); $this->installationToken = $this->extractInstallationToken($response); + Log::debug(sprintf('Installation ID: %s', serialize($this->installationId))); + Log::debug(sprintf('Installation token: %s', serialize($this->installationToken))); + Log::debug(sprintf('server public key: %s', serialize($this->serverPublicKey))); + return; } diff --git a/app/Services/Bunq/Request/ListDeviceServerRequest.php b/app/Services/Bunq/Request/ListDeviceServerRequest.php new file mode 100644 index 0000000000..ee9548b74a --- /dev/null +++ b/app/Services/Bunq/Request/ListDeviceServerRequest.php @@ -0,0 +1,74 @@ +devices = new Collection; + } + + /** + * @return Collection + */ + public function getDevices(): Collection + { + return $this->devices; + } + + + /** + * + */ + public function call(): void + { + $uri = '/v1/device-server'; + $data = []; + $headers = $this->getDefaultHeaders(); + $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); + $response = $this->sendSignedBunqGet($uri, $data, $headers); + + // create device server objects: + $raw = $this->getArrayFromResponse('DeviceServer', $response); + /** @var array $entry */ + foreach ($raw as $entry) { + $this->devices->push(new DeviceServer($entry)); + } + + return; + } + + /** + * @param InstallationToken $installationToken + */ + public function setInstallationToken(InstallationToken $installationToken) + { + $this->installationToken = $installationToken; + } +} \ No newline at end of file diff --git a/app/Support/Import/Prerequisites/BunqPrerequisites.php b/app/Support/Import/Prerequisites/BunqPrerequisites.php index f0cd1ed377..48e46c7717 100644 --- a/app/Support/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Support/Import/Prerequisites/BunqPrerequisites.php @@ -11,8 +11,21 @@ declare(strict_types=1); namespace FireflyIII\Support\Import\Prerequisites; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Services\Bunq\Id\DeviceServerId; +use FireflyIII\Services\Bunq\Object\DeviceServer; +use FireflyIII\Services\Bunq\Object\ServerPublicKey; +use FireflyIII\Services\Bunq\Request\DeviceServerRequest; +use FireflyIII\Services\Bunq\Request\InstallationTokenRequest; +use FireflyIII\Services\Bunq\Request\ListDeviceServerRequest; +use FireflyIII\Services\Bunq\Token\InstallationToken; use FireflyIII\User; +use Illuminate\Http\Request; +use Illuminate\Support\MessageBag; +use Log; use Preferences; +use Requests; +use Requests_Exception; /** * Class BunqPrerequisites @@ -68,4 +81,232 @@ class BunqPrerequisites implements PrerequisitesInterface return; } + + /** + * @param Request $request + * + * @return MessageBag + */ + public function storePrerequisites(Request $request): MessageBag + { + $apiKey = $request->get('api_key'); + Log::debug('Storing bunq API key'); + Preferences::setForUser($this->user, 'bunq_api_key', $apiKey); + // register Firefly III as a new device. + $serverId = $this->registerDevice(); + + + return new MessageBag; + } + + /** + * + */ + private function createKeyPair(): void + { + Log::debug('Generate new key pair for user.'); + $keyConfig = [ + "digest_alg" => "sha512", + "private_key_bits" => 2048, + "private_key_type" => OPENSSL_KEYTYPE_RSA, + ]; + // Create the private and public key + $res = openssl_pkey_new($keyConfig); + + // Extract the private key from $res to $privKey + $privKey = ''; + openssl_pkey_export($res, $privKey); + + // Extract the public key from $res to $pubKey + $pubKey = openssl_pkey_get_details($res); + + Preferences::setForUser($this->user, 'bunq_private_key', $privKey); + Preferences::setForUser($this->user, 'bunq_public_key', $pubKey['key']); + Log::debug('Created key pair'); + + return; + + } + + /** + * Get a list of servers and return the one that is this FF instance, if one can be found. + * + * @return DeviceServerId + * @throws FireflyException + */ + private function getExistingDevice(): DeviceServerId + { + $installationToken = $this->getInstallationToken(); + $serverPublicKey = $this->getServerPublicKey(); + $request = new ListDeviceServerRequest; + $remoteIp = $this->getRemoteIp(); + $request->setInstallationToken($installationToken); + $request->setServerPublicKey($serverPublicKey); + $request->setPrivateKey($this->getPrivateKey()); + $request->setServer(config('firefly.bunq.server')); + $request->call(); + $devices = $request->getDevices(); + /** @var DeviceServer $device */ + foreach ($devices as $device) { + if ($device->getIp() === $remoteIp) { + return $device->getId(); + } + } + throw new FireflyException('Cannot find existing Server Device that can be used by this instance of Firefly III.'); + } + + /** + * Get the installation token, either from the users preferences or from Bunq. + * + * @return InstallationToken + */ + private function getInstallationToken(): InstallationToken + { + Log::debug('Get installation token.'); + $token = Preferences::getForUser($this->user, 'bunq_installation_token', null); + if (!is_null($token)) { + return $token->data; + } + Log::debug('Have no token, request one.'); + + // verify bunq api code: + $publicKey = $this->getPublicKey(); + $request = new InstallationTokenRequest; + $request->setServer(strval(config('firefly.bunq.server'))); + $request->setPublicKey($publicKey); + $request->call(); + Log::debug('Sent request'); + + $installationToken = $request->getInstallationToken(); + $installationId = $request->getInstallationId(); + $serverPublicKey = $request->getServerPublicKey(); + + Preferences::setForUser($this->user, 'bunq_installation_token', $installationToken); + Preferences::setForUser($this->user, 'bunq_installation_id', $installationId); + Preferences::setForUser($this->user, 'bunq_server_public_key', $serverPublicKey); + + exit; + } + + /** + * Get the private key from the users preferences. + * + * @return string + */ + private function getPrivateKey(): string + { + Log::debug('get private key'); + $preference = Preferences::getForUser($this->user, 'bunq_private_key', null); + if (is_null($preference)) { + Log::debug('private key is null'); + // create key pair + $this->createKeyPair(); + } + $preference = Preferences::getForUser($this->user, 'bunq_private_key', null); + Log::debug('Return private key for user'); + + return $preference->data; + } + + /** + * Get a public key from the users preferences. + * + * @return string + */ + private function getPublicKey(): string + { + Log::debug('get public key'); + $preference = Preferences::getForUser($this->user, 'bunq_public_key', null); + if (is_null($preference)) { + Log::debug('public key is null'); + // create key pair + $this->createKeyPair(); + } + $preference = Preferences::getForUser($this->user, 'bunq_public_key', null); + Log::debug('Return public key for user'); + + return $preference->data; + } + + /** + * Let's assume this value will not change any time soon. + */ + private function getRemoteIp(): string + { + $preference = Preferences::getForUser($this->user, 'external_ip', null); + if (is_null($preference)) { + try { + $response = Requests::get('https://api.ipify.org'); + } catch (Requests_Exception $e) { + throw new FireflyException(sprintf('Could not retrieve external IP: %s', $e->getMessage())); + } + if ($response->status_code !== 200) { + throw new FireflyException(sprintf('Could not retrieve external IP: %d %s', $response->status_code, $response->body)); + } + $ip = $response->body; + Preferences::setForUser($this->user, 'external_ip', $ip); + + return $ip; + } + + return $preference->data; + } + + /** + * @return ServerPublicKey + */ + private function getServerPublicKey(): ServerPublicKey + { + return Preferences::getForUser($this->user, 'bunq_server_public_key', null)->data; + } + + /** + * To install Firefly III as a new device: + * - Send an installation token request. + * - Use this token to send a device server request + * - Store the installation token + * - Use the installation token each time we need a session. + */ + private function registerDevice(): DeviceServerId + { + Log::debug('Now in registerDevice'); + $deviceServerId = Preferences::getForUser($this->user, 'bunq_device_server_id', null); + $serverIp = $this->getRemoteIp(); + if (!is_null($deviceServerId)) { + Log::debug('Have device server ID.'); + + return $deviceServerId->data; + } + Log::debug('Device server id is null, do register.'); + $installationToken = $this->getInstallationToken(); + $serverPublicKey = $this->getServerPublicKey(); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key', ''); + $request = new DeviceServerRequest; + $request->setServer(strval(config('firefly.bunq.server'))); + $request->setPrivateKey($this->getPrivateKey()); + $request->setDescription('Firefly III v' . config('firefly.version') . ' for ' . $this->user->email); + $request->setSecret($apiKey->data); + $request->setPermittedIps([$serverIp]); + $request->setInstallationToken($installationToken); + $request->setServerPublicKey($serverPublicKey); + $deviceServerId = null; + // try to register device: + try { + $request->call(); + $deviceServerId = $request->getDeviceServerId(); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + if (is_null($deviceServerId)) { + // try get the current from a list: + $deviceServerId = $this->getExistingDevice(); + } + + Preferences::setForUser($this->user, 'bunq_device_server_id', $deviceServerId); + Log::debug(sprintf('Server ID: %s', serialize($deviceServerId))); + var_dump($deviceServerId); + var_dump($installationToken); + exit; + } + } diff --git a/app/Support/Import/Prerequisites/PrerequisitesInterface.php b/app/Support/Import/Prerequisites/PrerequisitesInterface.php index 803ee90e7e..393a3d82a5 100644 --- a/app/Support/Import/Prerequisites/PrerequisitesInterface.php +++ b/app/Support/Import/Prerequisites/PrerequisitesInterface.php @@ -13,6 +13,8 @@ namespace FireflyIII\Support\Import\Prerequisites; use FireflyIII\User; +use Illuminate\Http\Request; +use Illuminate\Support\MessageBag; interface PrerequisitesInterface { @@ -30,6 +32,13 @@ interface PrerequisitesInterface */ public function getViewParameters(): array; + /** + * @param Request $request + * + * @return MessageBag + */ + public function storePrerequisites(Request $request): MessageBag; + /** * Returns if this import method has any special prerequisites such as config * variables or other things. diff --git a/resources/views/import/bunq/prerequisites.twig b/resources/views/import/bunq/prerequisites.twig index 88872997a9..aafe01c93d 100644 --- a/resources/views/import/bunq/prerequisites.twig +++ b/resources/views/import/bunq/prerequisites.twig @@ -10,13 +10,13 @@
-

{{ trans('bank.prerequisites_for_import_title') }}

+

{{ trans('bank.bunq_prerequisites_title') }}

- {{ trans('bank.prerequisites_for_import_text') }} + {{ trans('bank.bunq_prerequisites_text') }}

diff --git a/routes/web.php b/routes/web.php index 0b0cba2265..13aa3714cf 100755 --- a/routes/web.php +++ b/routes/web.php @@ -401,6 +401,8 @@ Route::group( // banks: Route::get('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@prerequisites', 'as' => 'bank.prerequisites']); Route::post('bank/{bank}/prerequisites', ['uses' => 'Import\BankController@postPrerequisites', 'as' => 'bank.prerequisites.post']); + + Route::get('bank/{bank}/form', ['uses' => 'Import\BankController@form', 'as' => 'bank.form']); } ); From ca0f09c8f77a20bb76484446f0075010142da518 Mon Sep 17 00:00:00 2001 From: James Cole Date: Fri, 18 Aug 2017 23:02:29 +0200 Subject: [PATCH 057/668] Various cleanup. --- .../Controllers/Import/BankController.php | 15 ++++--- .../Prerequisites/BunqPrerequisites.php | 43 +++++++++++++------ 2 files changed, 38 insertions(+), 20 deletions(-) diff --git a/app/Http/Controllers/Import/BankController.php b/app/Http/Controllers/Import/BankController.php index 17af47128c..2e16476e8e 100644 --- a/app/Http/Controllers/Import/BankController.php +++ b/app/Http/Controllers/Import/BankController.php @@ -20,10 +20,13 @@ use Session; class BankController extends Controller { - + /** + * + */ public function form() { + } /** @@ -40,14 +43,12 @@ class BankController extends Controller $object = app($class); $object->setUser(auth()->user()); if (!$object->hasPrerequisites()) { - //Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank)); - //return redirect(route('import.bank.form', [$bank])); + Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank)); + return redirect(route('import.bank.form', [$bank])); } Log::debug('Going to store entered preprerequisites.'); // store post data $result = $object->storePrerequisites($request); - echo 'done with prereq'; - exit; if ($result->count() > 0) { Session::flash('error', $result->first()); @@ -70,12 +71,12 @@ class BankController extends Controller $object = app($class); $object->setUser(auth()->user()); - //if ($object->hasPrerequisites()) { + if ($object->hasPrerequisites()) { $view = $object->getView(); $parameters = $object->getViewParameters(); return view($view, $parameters); - //} + } if (!$object->hasPrerequisites()) { return redirect(route('import.bank.form', [$bank])); diff --git a/app/Support/Import/Prerequisites/BunqPrerequisites.php b/app/Support/Import/Prerequisites/BunqPrerequisites.php index 48e46c7717..44abdf488b 100644 --- a/app/Support/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Support/Import/Prerequisites/BunqPrerequisites.php @@ -28,7 +28,7 @@ use Requests; use Requests_Exception; /** - * Class BunqPrerequisites + * This class contains all the routines necessary to connect to Bunq. * * @package FireflyIII\Support\Import\Prerequisites */ @@ -38,7 +38,7 @@ class BunqPrerequisites implements PrerequisitesInterface private $user; /** - * Returns view name that allows user to fill in prerequisites. + * Returns view name that allows user to fill in prerequisites. Currently asks for the API key. * * @return string */ @@ -59,7 +59,8 @@ class BunqPrerequisites implements PrerequisitesInterface /** * Returns if this import method has any special prerequisites such as config - * variables or other things. + * variables or other things. The only thing we verify is the presence of the API key. Everything else + * tumbles into place: no installation token? Will be requested. No device server? Will be created. Etc. * * @return bool */ @@ -83,6 +84,9 @@ class BunqPrerequisites implements PrerequisitesInterface } /** + * This method responds to the user's submission of an API key. It tries to register this instance as a new Firefly III device. + * If this fails, the error is returned in a message bag and the user is notified (this is fairly friendly). + * * @param Request $request * * @return MessageBag @@ -93,14 +97,22 @@ class BunqPrerequisites implements PrerequisitesInterface Log::debug('Storing bunq API key'); Preferences::setForUser($this->user, 'bunq_api_key', $apiKey); // register Firefly III as a new device. - $serverId = $this->registerDevice(); + $serverId = null; + $messages = new MessageBag; + try { + $serverId = $this->registerDevice(); + Log::debug(sprintf('Found device server with id %d', $serverId->getId())); + } catch (FireflyException $e) { + $messages->add('error', $e->getMessage()); + } - - return new MessageBag; + return $messages; } /** - * + * This method creates a new public/private keypair for the user. This isn't really secure, since the key is generated on the fly with + * no regards for HSM's, smart cards or other things. It would require some low level programming to get this right. But the private key + * is stored encrypted in the database so it's something. */ private function createKeyPair(): void { @@ -129,7 +141,8 @@ class BunqPrerequisites implements PrerequisitesInterface } /** - * Get a list of servers and return the one that is this FF instance, if one can be found. + * When the device server cannot be registered for some reason (when previous attempts failed to be stored) this method can be used + * to try and detect the server ID for this firefly instance. * * @return DeviceServerId * @throws FireflyException @@ -185,7 +198,7 @@ class BunqPrerequisites implements PrerequisitesInterface Preferences::setForUser($this->user, 'bunq_installation_id', $installationId); Preferences::setForUser($this->user, 'bunq_server_public_key', $serverPublicKey); - exit; + return $installationToken; } /** @@ -229,7 +242,10 @@ class BunqPrerequisites implements PrerequisitesInterface } /** - * Let's assume this value will not change any time soon. + * Request users server remote IP. Let's assume this value will not change any time soon. + * + * @return string + * @throws FireflyException */ private function getRemoteIp(): string { @@ -253,6 +269,8 @@ class BunqPrerequisites implements PrerequisitesInterface } /** + * Get the public key of the server, necessary to verify server signature. + * * @return ServerPublicKey */ private function getServerPublicKey(): ServerPublicKey @@ -304,9 +322,8 @@ class BunqPrerequisites implements PrerequisitesInterface Preferences::setForUser($this->user, 'bunq_device_server_id', $deviceServerId); Log::debug(sprintf('Server ID: %s', serialize($deviceServerId))); - var_dump($deviceServerId); - var_dump($installationToken); - exit; + + return $deviceServerId; } } From 4694e31e35410ab4beea914e6894ead117b34bc1 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 19 Aug 2017 09:22:44 +0200 Subject: [PATCH 058/668] Expand bunq related code. --- .../Controllers/Import/BankController.php | 32 +++- .../Bunq/Object/MonetaryAccountBank.php | 23 +++ app/Services/Bunq/Object/UserCompany.php | 34 ++++ app/Services/Bunq/Object/UserPerson.php | 3 + app/Services/Bunq/Request/BunqRequest.php | 120 +++++++++++--- .../Request/DeleteDeviceSessionRequest.php | 49 ++++++ .../Bunq/Request/DeviceSessionRequest.php | 149 ++++++++++++++++++ .../Import/Information/BunqInformation.php | 105 ++++++++++++ .../Information/InformationInterface.php | 39 +++++ config/firefly.php | 3 + 10 files changed, 533 insertions(+), 24 deletions(-) create mode 100644 app/Services/Bunq/Object/MonetaryAccountBank.php create mode 100644 app/Services/Bunq/Object/UserCompany.php create mode 100644 app/Services/Bunq/Request/DeleteDeviceSessionRequest.php create mode 100644 app/Services/Bunq/Request/DeviceSessionRequest.php create mode 100644 app/Support/Import/Information/BunqInformation.php create mode 100644 app/Support/Import/Information/InformationInterface.php diff --git a/app/Http/Controllers/Import/BankController.php b/app/Http/Controllers/Import/BankController.php index 2e16476e8e..d54fa5726d 100644 --- a/app/Http/Controllers/Import/BankController.php +++ b/app/Http/Controllers/Import/BankController.php @@ -13,6 +13,7 @@ namespace FireflyIII\Http\Controllers\Import; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Support\Import\Information\InformationInterface; use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface; use Illuminate\Http\Request; use Log; @@ -21,15 +22,36 @@ use Session; class BankController extends Controller { /** - * + * This method must ask the user all parameters necessary to start importing data. This may not be enough + * to finish the import itself (ie. mapping) but it should be enough to begin: accounts to import from, + * accounts to import into, data ranges, etc. */ - public function form() + public function form(string $bank) { + $class = config(sprintf('firefly.import_pre.%s', $bank)); + /** @var PrerequisitesInterface $object */ + $object = app($class); + $object->setUser(auth()->user()); + if ($object->hasPrerequisites()) { + return redirect(route('import.banq.prerequisites', [$bank])); + } + $class = config(sprintf('firefly.import_info.%s', $bank)); + /** @var InformationInterface $object */ + $object = app($class); + $object->setUser(auth()->user()); + $remoteAccounts = $object->getAccounts(); } /** + * This method processes the prerequisites the user has entered in the previous step. + * + * Whatever storePrerequisites does, it should make sure that the system is ready to continue immediately. So + * no extra calls or stuff, except maybe to open a session + * + * @see PrerequisitesInterface::storePrerequisites + * * @param Request $request * @param string $bank * @@ -60,6 +82,9 @@ class BankController extends Controller } /** + * This method shows you, if necessary, a form that allows you to enter any required values, such as API keys, + * login passwords or other values. + * * @param string $bank * * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View @@ -77,10 +102,7 @@ class BankController extends Controller return view($view, $parameters); } - - if (!$object->hasPrerequisites()) { return redirect(route('import.bank.form', [$bank])); - } } } diff --git a/app/Services/Bunq/Object/MonetaryAccountBank.php b/app/Services/Bunq/Object/MonetaryAccountBank.php new file mode 100644 index 0000000000..31f7224be2 --- /dev/null +++ b/app/Services/Bunq/Object/MonetaryAccountBank.php @@ -0,0 +1,23 @@ +id = intval($data['id']); $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']); $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']); diff --git a/app/Services/Bunq/Request/BunqRequest.php b/app/Services/Bunq/Request/BunqRequest.php index 6712f178d6..629378377f 100644 --- a/app/Services/Bunq/Request/BunqRequest.php +++ b/app/Services/Bunq/Request/BunqRequest.php @@ -99,23 +99,24 @@ abstract class BunqRequest * @param string $data * * @return string + * @throws FireflyException */ protected function generateSignature(string $method, string $uri, array $headers, string $data): string { if (strlen($this->privateKey) === 0) { - throw new Exception('No private key present.'); + throw new FireflyException('No private key present.'); } - if (strtolower($method) === 'get') { + if (strtolower($method) === 'get' || strtolower($method) === 'delete') { $data = ''; } $uri = str_replace(['https://api.bunq.com', 'https://sandbox.public.api.bunq.com'], '', $uri); - $toSign = strtoupper($method) . ' ' . $uri . "\n"; + $toSign = sprintf("%s %s\n", strtoupper($method), $uri); $headersToSign = ['Cache-Control', 'User-Agent']; ksort($headers); foreach ($headers as $name => $value) { if (in_array($name, $headersToSign) || substr($name, 0, 7) === 'X-Bunq-') { - $toSign .= $name . ': ' . $value . "\n"; + $toSign .= sprintf("%s: %s\n", $name, $value); } } $toSign .= "\n" . $data; @@ -184,6 +185,48 @@ abstract class BunqRequest return []; } + /** + * @param string $uri + * @param array $headers + * + * @return array + * @throws Exception + */ + protected function sendSignedBunqDelete(string $uri, array $headers): array + { + if (strlen($this->server) === 0) { + throw new FireflyException('No bunq server defined'); + } + + $fullUri = $this->server . $uri; + $signature = $this->generateSignature('delete', $uri, $headers, ''); + $headers['X-Bunq-Client-Signature'] = $signature; + try { + $response = Requests::delete($fullUri, $headers); + } catch (Requests_Exception $e) { + return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; + } + + $body = $response->body; + $array = json_decode($body, true); + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + $array['ResponseHeaders'] = $responseHeaders; + $array['ResponseStatusCode'] = $statusCode; + + Log::debug(sprintf('Response to DELETE %s is %s', $fullUri, $body)); + if ($this->isErrorResponse($array)) { + $this->throwResponseError($array); + } + + if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { + throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); + } + + + return $array; + } + /** * @param string $uri * @param array $data @@ -208,17 +251,20 @@ abstract class BunqRequest return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; } - $body = $response->body; - $array = json_decode($body, true); + $body = $response->body; + $array = json_decode($body, true); + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + $array['ResponseHeaders'] = $responseHeaders; + $array['ResponseStatusCode'] = $statusCode; + if ($this->isErrorResponse($array)) { $this->throwResponseError($array); } - $responseHeaders = $response->headers->getAll(); - $statusCode = $response->status_code; + if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); } - $array['ResponseHeaders'] = $responseHeaders; return $array; } @@ -243,17 +289,49 @@ abstract class BunqRequest return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; } - $body = $response->body; - $array = json_decode($body, true); + $body = $response->body; + $array = json_decode($body, true); + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + $array['ResponseHeaders'] = $responseHeaders; + $array['ResponseStatusCode'] = $statusCode; + if ($this->isErrorResponse($array)) { $this->throwResponseError($array); } - $responseHeaders = $response->headers->getAll(); - $statusCode = $response->status_code; + if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) { throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri)); } - $array['ResponseHeaders'] = $responseHeaders; + + + return $array; + } + + /** + * @param string $uri + * @param array $headers + * + * @return array + */ + protected function sendUnsignedBunqDelete(string $uri, array $headers): array + { + $fullUri = $this->server . $uri; + try { + $response = Requests::delete($fullUri, $headers); + } catch (Requests_Exception $e) { + return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; + } + $body = $response->body; + $array = json_decode($body, true); + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + $array['ResponseHeaders'] = $responseHeaders; + $array['ResponseStatusCode'] = $statusCode; + + if ($this->isErrorResponse($array)) { + $this->throwResponseError($array); + } return $array; } @@ -274,13 +352,17 @@ abstract class BunqRequest } catch (Requests_Exception $e) { return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]]; } - $body = $response->body; - $responseHeaders = $response->headers->getAll(); - $array = json_decode($body, true); + $body = $response->body; + $array = json_decode($body, true); + $responseHeaders = $response->headers->getAll(); + $statusCode = $response->status_code; + $array['ResponseHeaders'] = $responseHeaders; + $array['ResponseStatusCode'] = $statusCode; + if ($this->isErrorResponse($array)) { $this->throwResponseError($array); } - $array['ResponseHeaders'] = $responseHeaders; + return $array; } @@ -313,7 +395,7 @@ abstract class BunqRequest $message[] = $error['error_description']; } } - throw new FireflyException(join(', ', $message)); + throw new FireflyException('Bunq ERROR ' . $response['ResponseStatusCode'] . ': ' . join(', ', $message)); } /** diff --git a/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php b/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php new file mode 100644 index 0000000000..66c5d1eecc --- /dev/null +++ b/app/Services/Bunq/Request/DeleteDeviceSessionRequest.php @@ -0,0 +1,49 @@ +sessionToken->getId()); + $headers = $this->getDefaultHeaders(); + $headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken(); + $this->sendSignedBunqDelete($uri, $headers); + return; + } + + /** + * @param SessionToken $sessionToken + */ + public function setSessionToken(SessionToken $sessionToken) + { + $this->sessionToken = $sessionToken; + } +} \ No newline at end of file diff --git a/app/Services/Bunq/Request/DeviceSessionRequest.php b/app/Services/Bunq/Request/DeviceSessionRequest.php new file mode 100644 index 0000000000..66bc0dc768 --- /dev/null +++ b/app/Services/Bunq/Request/DeviceSessionRequest.php @@ -0,0 +1,149 @@ + $this->secret]; + $headers = $this->getDefaultHeaders(); + $headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken(); + $response = $this->sendSignedBunqPost($uri, $data, $headers); + + + $this->deviceSessionId = $this->extractDeviceSessionId($response); + $this->sessionToken = $this->extractSessionToken($response); + $this->userPerson = $this->extractUserPerson($response); + $this->userCompany = $this->extractUserCompany($response); + + Log::debug(sprintf('Session ID: %s', serialize($this->deviceSessionId))); + Log::debug(sprintf('Session token: %s', serialize($this->sessionToken))); + Log::debug(sprintf('Session user person: %s', serialize($this->userPerson))); + Log::debug(sprintf('Session user company: %s', serialize($this->userCompany))); + + return; + } + + /** + * @return DeviceSessionId + */ + public function getDeviceSessionId(): DeviceSessionId + { + return $this->deviceSessionId; + } + + /** + * @return SessionToken + */ + public function getSessionToken(): SessionToken + { + return $this->sessionToken; + } + + /** + * @return UserPerson + */ + public function getUserPerson(): UserPerson + { + return $this->userPerson; + } + + /** + * @param InstallationToken $installationToken + */ + public function setInstallationToken(InstallationToken $installationToken) + { + $this->installationToken = $installationToken; + } + + /** + * @param array $response + * + * @return DeviceSessionId + */ + private function extractDeviceSessionId(array $response): DeviceSessionId + { + $data = $this->getKeyFromResponse('Id', $response); + $deviceSessionId = new DeviceSessionId; + $deviceSessionId->setId(intval($data['id'])); + + return $deviceSessionId; + } + + private function extractSessionToken(array $response): SessionToken + { + $data = $this->getKeyFromResponse('Token', $response); + $sessionToken = new SessionToken($data); + + return $sessionToken; + } + + /** + * @param $response + * + * @return UserCompany + */ + private function extractUserCompany($response): UserCompany + { + $data = $this->getKeyFromResponse('UserCompany', $response); + $userCompany = new UserCompany($data); + + + return $userCompany; + } + + /** + * @param $response + * + * @return UserPerson + */ + private function extractUserPerson($response): UserPerson + { + $data = $this->getKeyFromResponse('UserPerson', $response); + $userPerson = new UserPerson($data); + + + return $userPerson; + } + + +} \ No newline at end of file diff --git a/app/Support/Import/Information/BunqInformation.php b/app/Support/Import/Information/BunqInformation.php new file mode 100644 index 0000000000..971336df75 --- /dev/null +++ b/app/Support/Import/Information/BunqInformation.php @@ -0,0 +1,105 @@ +startSession(); + + // get list of Bunq accounts: + + + $this->closeSession($sessionToken); + + return new Collection; + } + + /** + * Set the user for this Prerequisites-routine. Class is expected to implement and save this. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } + + /** + * @param SessionToken $sessionToken + */ + private function closeSession(SessionToken $sessionToken): void + { + Log::debug('Going to close session'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $server = config('firefly.bunq.server'); + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $request = new DeleteDeviceSessionRequest(); + $request->setSecret($apiKey); + $request->setServer($server); + $request->setPrivateKey($privateKey); + $request->setServerPublicKey($serverPublicKey); + $request->setSessionToken($sessionToken); + $request->call(); + return; + } + + /** + * @return SessionToken + */ + private function startSession(): SessionToken + { + Log::debug('Now in startSession.'); + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $server = config('firefly.bunq.server'); + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $installationToken = Preferences::getForUser($this->user, 'bunq_installation_token')->data; + $request = new DeviceSessionRequest(); + $request->setSecret($apiKey); + $request->setServerPublicKey($serverPublicKey); + $request->setServer($server); + $request->setPrivateKey($privateKey); + $request->setInstallationToken($installationToken); + $request->call(); + $sessionToken = $request->getSessionToken(); + Log::debug(sprintf('Now have got session token: %s', serialize($sessionToken))); + + return $sessionToken; + } +} \ No newline at end of file diff --git a/app/Support/Import/Information/InformationInterface.php b/app/Support/Import/Information/InformationInterface.php new file mode 100644 index 0000000000..a3a8fb6dca --- /dev/null +++ b/app/Support/Import/Information/InformationInterface.php @@ -0,0 +1,39 @@ + [ 'bunq' => 'FireflyIII\Support\Import\Prerequisites\BunqPrerequisites', ], + 'import_info' => [ + 'bunq' => 'FireflyIII\Support\Import\Information\BunqInformation', + ], 'bunq' => [ 'server' => 'https://sandbox.public.api.bunq.com', ], From c4fe9a6a51e09a0c4ff5e13cd2ec6e9dc41599b9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 19 Aug 2017 16:46:06 +0200 Subject: [PATCH 059/668] More code for bunq import, must now wait for reply from bunq itself --- .../Bunq/Object/MonetaryAccountBank.php | 8 ++ app/Services/Bunq/Object/UserCompany.php | 63 +++++++++++++ app/Services/Bunq/Object/UserLight.php | 70 +++++++++++++++ app/Services/Bunq/Object/UserPerson.php | 9 ++ app/Services/Bunq/Request/ListUserRequest.php | 90 +++++++++++++++++++ .../Import/Information/BunqInformation.php | 27 ++++++ 6 files changed, 267 insertions(+) create mode 100644 app/Services/Bunq/Object/UserLight.php create mode 100644 app/Services/Bunq/Request/ListUserRequest.php diff --git a/app/Services/Bunq/Object/MonetaryAccountBank.php b/app/Services/Bunq/Object/MonetaryAccountBank.php index 31f7224be2..2642d0ac5f 100644 --- a/app/Services/Bunq/Object/MonetaryAccountBank.php +++ b/app/Services/Bunq/Object/MonetaryAccountBank.php @@ -19,5 +19,13 @@ namespace FireflyIII\Services\Bunq\Object; */ class MonetaryAccountBank extends BunqObject { + /** + * MonetaryAccountBank constructor. + * + * @param array $data + */ + public function __construct(array $data) { + + } } \ No newline at end of file diff --git a/app/Services/Bunq/Object/UserCompany.php b/app/Services/Bunq/Object/UserCompany.php index 9ae3f18931..c0c861f947 100644 --- a/app/Services/Bunq/Object/UserCompany.php +++ b/app/Services/Bunq/Object/UserCompany.php @@ -21,6 +21,52 @@ use Carbon\Carbon; */ class UserCompany extends BunqObject { + private $addressMain; + private $addressPostal; + /** @var array */ + private $aliases = []; + private $avatar; + /** @var string */ + private $cocNumber = ''; + /** @var string */ + private $counterBankIban = ''; + /** @var Carbon */ + private $created; + private $dailyLimit; + private $directorAlias; + /** @var string */ + private $displayName = ''; + /** @var int */ + private $id = 0; + /** @var string */ + private $language = ''; + /** @var string */ + private $name = ''; + /** @var array */ + private $notificationFilters = []; + /** @var string */ + private $publicNickName = ''; + /** @var string */ + private $publicUuid = ''; + /** @var string */ + private $region = ''; + /** @var string */ + private $sectorOfIndustry = ''; + /** @var int */ + private $sessionTimeout = 0; + /** @var string */ + private $status = ''; + /** @var string */ + private $subStatus = ''; + /** @var string */ + private $typeOfBusinessEntity = ''; + /** @var array */ + private $ubos = []; + /** @var Carbon */ + private $updated; + /** @var int */ + private $versionTos = 0; + /** * UserCompany constructor. * @@ -28,6 +74,23 @@ class UserCompany extends BunqObject */ public function __construct(array $data) { + $this->id = intval($data['id']); + $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']); + $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']); + $this->status = $data['status']; + $this->subStatus = $data['sub_status']; + $this->publicUuid = $data['public_uuid']; + $this->displayName = $data['display_name']; + $this->publicNickName = $data['public_nick_name']; + $this->language = $data['language']; + $this->region = $data['region']; + $this->sessionTimeout = intval($data['session_timeout']); + $this->versionTos = intval($data['version_terms_of_service']); + $this->cocNumber = $data['chamber_of_commerce_number']; + $this->typeOfBusinessEntity = $data['type_of_business_entity'] ?? ''; + $this->sectorOfIndustry = $data['sector_of_industry'] ?? ''; + $this->counterBankIban = $data['counter_bank_iban']; + $this->name = $data['name']; } diff --git a/app/Services/Bunq/Object/UserLight.php b/app/Services/Bunq/Object/UserLight.php new file mode 100644 index 0000000000..fe47df54a7 --- /dev/null +++ b/app/Services/Bunq/Object/UserLight.php @@ -0,0 +1,70 @@ +id = intval($data['id']); + $this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']); + $this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']); + $this->publicUuid = $data['public_uuid']; + $this->displayName = $data['display_name']; + $this->publicNickName = $data['public_nick_name']; + $this->firstName = $data['first_name']; + $this->middleName = $data['middle_name']; + $this->lastName = $data['last_name']; + $this->legalName = $data['legal_name']; + // aliases + } + +} \ No newline at end of file diff --git a/app/Services/Bunq/Object/UserPerson.php b/app/Services/Bunq/Object/UserPerson.php index 336d7c9a4f..c9a29ebb4d 100644 --- a/app/Services/Bunq/Object/UserPerson.php +++ b/app/Services/Bunq/Object/UserPerson.php @@ -134,4 +134,13 @@ class UserPerson extends BunqObject // echo ''; } + /** + * @return int + */ + public function getId(): int + { + return $this->id; + } + + } \ No newline at end of file diff --git a/app/Services/Bunq/Request/ListUserRequest.php b/app/Services/Bunq/Request/ListUserRequest.php new file mode 100644 index 0000000000..06dbeb96de --- /dev/null +++ b/app/Services/Bunq/Request/ListUserRequest.php @@ -0,0 +1,90 @@ +getDefaultHeaders(); + $headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken(); + $response = $this->sendSignedBunqGet($uri, $data, $headers); + + // create user objects: + $light = $this->getKeyFromResponse('UserLight', $response); + $company = $this->getKeyFromResponse('UserCompany', $response); + $person = $this->getKeyFromResponse('UserPerson', $response); + $this->userLight = new UserLight($light); + $this->userCompany = new UserCompany($company); + $this->userPerson = new UserPerson($person); + + return; + } + + /** + * @return UserCompany + */ + public function getUserCompany(): UserCompany + { + return $this->userCompany; + } + + /** + * @return UserLight + */ + public function getUserLight(): UserLight + { + return $this->userLight; + } + + /** + * @return UserPerson + */ + public function getUserPerson(): UserPerson + { + return $this->userPerson; + } + + + /** + * @param SessionToken $sessionToken + */ + public function setSessionToken(SessionToken $sessionToken) + { + $this->sessionToken = $sessionToken; + } +} \ No newline at end of file diff --git a/app/Support/Import/Information/BunqInformation.php b/app/Support/Import/Information/BunqInformation.php index 971336df75..3c41800fa1 100644 --- a/app/Support/Import/Information/BunqInformation.php +++ b/app/Support/Import/Information/BunqInformation.php @@ -14,6 +14,7 @@ namespace FireflyIII\Support\Import\Information; use FireflyIII\Services\Bunq\Request\DeleteDeviceSessionRequest; use FireflyIII\Services\Bunq\Request\DeviceSessionRequest; +use FireflyIII\Services\Bunq\Request\ListUserRequest; use FireflyIII\Services\Bunq\Token\SessionToken; use FireflyIII\User; use Illuminate\Support\Collection; @@ -40,6 +41,7 @@ class BunqInformation implements InformationInterface { Log::debug('Now in getAccounts()'); $sessionToken = $this->startSession(); + $this->getUserInformation($sessionToken); // get list of Bunq accounts: @@ -79,6 +81,31 @@ class BunqInformation implements InformationInterface return; } + /** + * @param SessionToken $sessionToken + */ + private function getUserInformation(SessionToken $sessionToken): void + { + $apiKey = Preferences::getForUser($this->user, 'bunq_api_key')->data; + $serverPublicKey = Preferences::getForUser($this->user, 'bunq_server_public_key')->data; + $server = config('firefly.bunq.server'); + $privateKey = Preferences::getForUser($this->user, 'bunq_private_key')->data; + $request = new ListUserRequest; + $request->setSessionToken($sessionToken); + $request->setSecret($apiKey); + $request->setServerPublicKey($serverPublicKey); + $request->setServer($server); + $request->setPrivateKey($privateKey); + $request->call(); + // return the first that isn't null? + // get all objects, try to find ID. + var_dump($request->getUserCompany()); + var_dump($request->getUserLight()); + var_dump($request->getUserPerson()); + + return; + } + /** * @return SessionToken */ From bd924b993b31a979b41ee3b5fce95a5abb914336 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 19 Aug 2017 18:24:04 +0200 Subject: [PATCH 060/668] This fixes part of #738 (not everything). --- .../Controllers/Transaction/SplitController.php | 14 ++++++++++++-- resources/views/transactions/split/edit.twig | 13 ++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index 23225eae8f..d8c8a872f9 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -18,6 +18,7 @@ use ExpandedForm; use FireflyIII\Events\UpdatedTransactionJournal; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -94,13 +95,22 @@ class SplitController extends Controller $uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size'))); $currencies = $this->currencies->get(); - $assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])); + $accountList = $this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + $assetAccounts = ExpandedForm::makeSelectList($accountList); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; $budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets()); $preFilled = $this->arrayFromJournal($request, $journal); $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); $subTitleIcon = 'fa-pencil'; + $accountArray = []; + // account array to display currency info: + /** @var Account $account */ + foreach ($accountList as $account) { + $accountArray[$account->id] = $account; + } + + Session::flash('gaEventCategory', 'transactions'); Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']); @@ -115,7 +125,7 @@ class SplitController extends Controller compact( 'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'assetAccounts', - 'budgets', 'journal' + 'budgets', 'journal','accountArray' ) ); } diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index dfb69c7fcd..5266ee0be0 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -56,9 +56,16 @@ {% endif %} {# TOTAL AMOUNT IS STATIC TEXT #} - {# TODO this does not reflect the actual currency (currencies) #} - {{ ExpandedForm.staticText('journal_amount', preFilled.journal_amount|formatAmount ) }} - + {% if preFilled.what == 'withdrawal' or preFilled.what == 'transfer' %} + {{ ExpandedForm.staticText('journal_amount', formatAmountByAccount(accountArray[preFilled.journal_source_account_id], preFilled.journal_amount, true) ) }} + + {% endif %} + + {% if preFilled.what == 'deposit' %} + {{ ExpandedForm.staticText('journal_amount', formatAmountByAccount(accountArray[preFilled.journal_destination_account_id], preFilled.journal_amount, true) ) }} + + {% endif %} + {# DATE #} {{ ExpandedForm.date('date', journal.date) }} From 2609f3425b7e01e0ea944b6fd6eb8cd911e06c01 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 19 Aug 2017 21:59:13 +0200 Subject: [PATCH 061/668] Fix #738 --- .../Transaction/SplitController.php | 5 +-- public/js/ff/transactions/split/edit.js | 7 ++-- resources/views/transactions/split/edit.twig | 35 ++++++++++++------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php index d8c8a872f9..2f0b407cc9 100644 --- a/app/Http/Controllers/Transaction/SplitController.php +++ b/app/Http/Controllers/Transaction/SplitController.php @@ -107,7 +107,8 @@ class SplitController extends Controller // account array to display currency info: /** @var Account $account */ foreach ($accountList as $account) { - $accountArray[$account->id] = $account; + $accountArray[$account->id] = $account; + $accountArray[$account->id]['currency_id'] = intval($account->getMeta('currency_id')); } @@ -125,7 +126,7 @@ class SplitController extends Controller compact( 'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'assetAccounts', - 'budgets', 'journal','accountArray' + 'budgets', 'journal', 'accountArray' ) ); } diff --git a/public/js/ff/transactions/split/edit.js b/public/js/ff/transactions/split/edit.js index dbe9cfba98..197e9f6fb6 100644 --- a/public/js/ff/transactions/split/edit.js +++ b/public/js/ff/transactions/split/edit.js @@ -7,7 +7,7 @@ */ -/** global: originalSum, accounting, what, Modernizr */ +/** global: originalSum, accounting, what, Modernizr, currencySymbol */ var destAccounts = {}; var srcAccounts = {}; @@ -231,13 +231,14 @@ function calculateSum() { sum = Math.round(sum * 100) / 100; left = Math.round(left * 100) / 100; + $('.amount-warning').remove(); if (sum !== originalSum) { var holder = $('#journal_amount_holder'); var par = holder.find('p.form-control-static'); - $('').text(' (' + accounting.formatMoney(sum) + ')').addClass('text-danger amount-warning').appendTo(par); + $('').text(' (' + accounting.formatMoney(sum, currencySymbol) + ')').addClass('text-danger amount-warning').appendTo(par); // also add what's left to divide (or vice versa) - $('').text(' (' + accounting.formatMoney(left) + ')').addClass('text-danger amount-warning').appendTo(par); + $('').text(' (' + accounting.formatMoney(left, currencySymbol) + ')').addClass('text-danger amount-warning').appendTo(par); } } \ No newline at end of file diff --git a/resources/views/transactions/split/edit.twig b/resources/views/transactions/split/edit.twig index 5266ee0be0..fcce6ddc94 100644 --- a/resources/views/transactions/split/edit.twig +++ b/resources/views/transactions/split/edit.twig @@ -250,7 +250,11 @@ {% endif %} {# amount#} + {% if transaction.foreign_amount != null %}
+ {% else %} +
+ {% endif %}
{{ transaction.transaction_currency_symbol }}
{# foreign amount #} + {% if transaction.foreign_amount != null %}
- {% if transaction.foreign_amount != null %} +
+
{{ transaction.foreign_currency_symbol }}
+ +
+ -
-
{{ transaction.foreign_currency_symbol }}
- -
- - - {% endif %}
+ {% endif %} {# budget #} {% if preFilled.what == 'withdrawal' %} @@ -330,7 +333,15 @@ {% endblock %} {% block scripts %} + From 1d6f3fc57f3401e7e31f309c17f287fe141c04aa Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:39:31 +0200 Subject: [PATCH 062/668] Update composer file. --- composer.lock | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/composer.lock b/composer.lock index 55445326ed..b3fdfc23cd 100644 --- a/composer.lock +++ b/composer.lock @@ -2067,16 +2067,16 @@ }, { "name": "symfony/polyfill-mbstring", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937" + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/f29dca382a6485c3cbe6379f0c61230167681937", - "reference": "f29dca382a6485c3cbe6379f0c61230167681937", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", "shasum": "" }, "require": { @@ -2088,7 +2088,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -2122,20 +2122,20 @@ "portable", "shim" ], - "time": "2017-06-09T14:24:12+00:00" + "time": "2017-06-14T15:44:48+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929" + "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/bc0b7d6cb36b10cfabb170a3e359944a95174929", - "reference": "bc0b7d6cb36b10cfabb170a3e359944a95174929", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e85ebdef569b84e8709864e1a290c40f156b30ca", + "reference": "e85ebdef569b84e8709864e1a290c40f156b30ca", "shasum": "" }, "require": { @@ -2145,7 +2145,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -2178,20 +2178,20 @@ "portable", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-06-14T15:44:48+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.4.0", + "version": "v1.5.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d" + "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", - "reference": "ebccbde4aad410f6438d86d7d261c6b4d2b9a51d", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/67925d1cf0b84bd234a83bebf26d4eb281744c6d", + "reference": "67925d1cf0b84bd234a83bebf26d4eb281744c6d", "shasum": "" }, "require": { @@ -2200,7 +2200,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.5-dev" } }, "autoload": { @@ -2230,7 +2230,7 @@ "polyfill", "shim" ], - "time": "2017-06-09T08:25:21+00:00" + "time": "2017-07-05T15:09:33+00:00" }, { "name": "symfony/process", @@ -3697,16 +3697,16 @@ }, { "name": "phpunit/php-token-stream", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b" + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/ecb0b2cdaa0add708fe6f329ef65ae0c5225130b", - "reference": "ecb0b2cdaa0add708fe6f329ef65ae0c5225130b", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/9a02332089ac48e704c70f6cefed30c224e3c0b0", + "reference": "9a02332089ac48e704c70f6cefed30c224e3c0b0", "shasum": "" }, "require": { @@ -3742,7 +3742,7 @@ "keywords": [ "tokenizer" ], - "time": "2017-08-03T14:17:41+00:00" + "time": "2017-08-20T05:47:52+00:00" }, { "name": "phpunit/phpunit", From 5769d0121e90565c0de760840b1611c6d5de09e9 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:40:06 +0200 Subject: [PATCH 063/668] New translations intro.php (Dutch) --- resources/lang/nl_NL/intro.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/lang/nl_NL/intro.php b/resources/lang/nl_NL/intro.php index fc788e515e..dfac61180c 100644 --- a/resources/lang/nl_NL/intro.php +++ b/resources/lang/nl_NL/intro.php @@ -21,8 +21,8 @@ return [ // create account: 'accounts_create_iban' => 'Geef je rekeningen een geldige IBAN. Dat scheelt met importeren van data.', 'accounts_create_asset_opening_balance' => 'Betaalrekeningen kunnen een startsaldo hebben, waarmee het begin van deze rekening in Firefly wordt aangegeven.', - 'accounts_create_asset_currency' => 'Firefly III supports multiple currencies. Asset accounts have one main currency, which you must set here.', - 'accounts_create_asset_virtual' => 'It can sometimes help to give your account a virtual balance: an extra amount always added to or removed from the actual balance.', + 'accounts_create_asset_currency' => 'Firefly III ondersteunt meerdere valuta. Hier stel je de valuta in van je betaalrekening.', + 'accounts_create_asset_virtual' => 'Soms is het handig om je betaalrekening een virtueel saldo te geven: een extra bedrag dat altijd bij het daadwerkelijke saldo wordt opgeteld.', // budgets index 'budgets_index_intro' => 'Budgets are used to manage your finances and form one of the core functions of Firefly III.', From 40639dfa37660cf7eb69b293cf615b59c2bab131 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:40:14 +0200 Subject: [PATCH 064/668] New view for tags --- app/Http/Controllers/TagController.php | 47 +++----- app/Repositories/Tag/TagRepository.php | 69 ++++++++++- .../Tag/TagRepositoryInterface.php | 9 ++ public/css/firefly.css | 4 + resources/views/tags/index.twig | 113 +++++++++++------- resources/views/tags/show.twig | 4 +- 6 files changed, 168 insertions(+), 78 deletions(-) diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 5dcbe252cb..64d1d22701 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -168,41 +168,23 @@ class TagController extends Controller */ public function index(TagRepositoryInterface $repository) { - $title = 'Tags'; - $mainTitleIcon = 'fa-tags'; - $types = ['nothing', 'balancingAct', 'advancePayment']; - $hasTypes = 0; // which types of tag the user actually has. - $counts = []; // how many of each type? - $count = $repository->count(); - // loop each types and get the tags, group them by year. - $collection = []; - foreach ($types as $type) { + // collect tags by year: + /** @var Carbon $start */ + $start = clone(session('first')); + $now = new Carbon; + $clouds = []; + $clouds['no-date'] = $repository->tagCloud(null); + while ($now > $start) { + $year = $now->year; + $clouds[$year] = $repository->tagCloud($year); - /** @var Collection $tags */ - $tags = $repository->getByType($type); - $tags = $tags->sortBy( - function (Tag $tag) { - $date = !is_null($tag->date) ? $tag->date->format('Ymd') : '000000'; - - return strtolower($date . $tag->tag); - } - ); - if ($tags->count() > 0) { - $hasTypes++; - } - $counts[$type] = $tags->count(); - - /** @var Tag $tag */ - foreach ($tags as $tag) { - $year = is_null($tag->date) ? trans('firefly.no_year') : $tag->date->year; - $monthFormatted = is_null($tag->date) ? trans('firefly.no_month') : $tag->date->formatLocalized($this->monthFormat); - - $collection[$type][$year][$monthFormatted][] = $tag; - } + $now->subYear(); } + $count = $repository->count(); - return view('tags.index', compact('title', 'mainTitleIcon', 'counts', 'hasTypes', 'types', 'collection', 'count')); + + return view('tags.index', compact('clouds', 'count')); } /** @@ -274,7 +256,6 @@ class TagController extends Controller return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment')); } - /** * @param TagFormRequest $request * @@ -371,4 +352,6 @@ class TagController extends Controller return $collection; } + + } diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index e81201ef0b..01910c3a0f 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -253,11 +253,61 @@ class TagRepository implements TagRepositoryInterface } $collector->setAllAssetAccounts()->setTag($tag); - $sum = $collector->getJournals()->sum('transaction_amount'); + $journals = $collector->getJournals(); + $sum = '0'; + foreach ($journals as $journal) { + $sum = bcadd($sum, app('steam')->positive(strval($journal->transaction_amount))); + } return strval($sum); } + /** + * Generates a tag cloud. + * + * @param int|null $year + * + * @return array + */ + public function tagCloud(?int $year): array + { + $min = null; + $max = 0; + $query = $this->user->tags(); + $return = []; + if (!is_null($year)) { + $start = $year . '-01-01'; + $end = $year . '-12-31'; + $query->where('date', '>=', $start)->where('date', '<=', $end); + } + if (is_null($year)) { + $query->whereNull('date'); + } + $tags = $query->orderBy('id','desc')->get(); + $temporary = []; + foreach ($tags as $tag) { + $amount = floatval($this->sumOfTag($tag, null, null)); + $min = $amount < $min || is_null($min) ? $amount : $min; + $max = $amount > $max ? $amount : $max; + $temporary[] = [ + 'amount' => $amount, + 'tag' => $tag, + ]; + } + /** @var array $entry */ + foreach ($temporary as $entry) { + $scale = $this->cloudScale([12, 20], $entry['amount'], $min, $max); + $tagId = $entry['tag']->id; + $return[$tagId] = [ + 'scale' => $scale, + 'tag' => $entry['tag'], + ]; + } + + return $return; + } + + /** * @param Tag $tag * @param array $data @@ -277,4 +327,21 @@ class TagRepository implements TagRepositoryInterface return $tag; } + /** + * @param array $range + * @param float $amount + * @param float $min + * @param float $max + * + * @return int + */ + private function cloudScale(array $range, float $amount, float $min, float $max): int + { + $amountDiff = $max - $min; + $diff = $range[1] - $range[0]; + $step = $amountDiff / $diff; + $extra = round($amount / $step); + + return intval($range[0] + $extra); + } } diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index 8cc71547ba..6ed8ec4edd 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -135,6 +135,15 @@ interface TagRepositoryInterface */ public function sumOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string; + /** + * Generates a tag cloud. + * + * @param int|null $year + * + * @return array + */ + public function tagCloud(?int $year): array; + /** * Update a tag. * diff --git a/public/css/firefly.css b/public/css/firefly.css index 4957efdbfa..b5fd1b056c 100644 --- a/public/css/firefly.css +++ b/public/css/firefly.css @@ -17,6 +17,10 @@ background: url('/images/error.png') no-repeat center center; } +p.tagcloud .label { + line-height:2; +} + .handle { cursor: move; } diff --git a/resources/views/tags/index.twig b/resources/views/tags/index.twig index 6d1531cd7a..7365efda52 100644 --- a/resources/views/tags/index.twig +++ b/resources/views/tags/index.twig @@ -8,54 +8,81 @@ {% if count == 0 %} {% include 'partials.empty' with {what: 'default', type: 'tags',route: route('tags.create')} %} {% else %} -
- {% for type in types %} - {% if counts[type] > 0 %} -
-
-
-

{{ ('tag_title_'~type)|_ }}

-
-
- {% for year,months in collection[type] %} -

{{ year }}

- - {% for month,tags in months %} -
{{ month }}
-

- {% for tag in tags %} - - {% if tag.tagMode == 'nothing' %} - - {% endif %} - {% if tag.tagMode == 'balancingAct' %} - - {% endif %} - {% if tag.tagMode == 'advancePayment' %} - - {% endif %} - {{ tag.tag }} - - {% endfor %} -

- {% endfor %} + {% for period,entries in clouds %} + {% if entries|length > 0 %} +
+
+
+
+

+ {% if period == 'no-date' %}{{ 'without_date'|_ }}{% else %}{{ period }}{% endif %} +

+
+
+

+ {% for tagInfo in entries %} + {{ tagInfo.tag.tag }} {% endfor %} - - -

- +

+
+
{% endif %} - {% endfor %} + {% endfor %} + {# + +
+ + {% for type in types %} + {% if counts[type] > 0 %} +
+
+
+

{{ ('tag_title_'~type)|_ }}

+
+
+ {% for year,months in collection[type] %} +

{{ year }}

+ + {% for month,tags in months %} +
{{ month }}
+

+ {% for tag in tags %} + + {% if tag.tagMode == 'nothing' %} + + {% endif %} + {% if tag.tagMode == 'balancingAct' %} + + {% endif %} + {% if tag.tagMode == 'advancePayment' %} + + {% endif %} + {{ tag.tag }} + + {% endfor %} +

+ {% endfor %} + {% endfor %} + + +
+ +
+
+ {% endif %} + {% endfor %} +
+ #} {% endif %} {% endblock %} diff --git a/resources/views/tags/show.twig b/resources/views/tags/show.twig index 3237078e90..acd4a376ad 100644 --- a/resources/views/tags/show.twig +++ b/resources/views/tags/show.twig @@ -109,14 +109,14 @@ {% if periods.count > 0 %}

- + {{ 'show_all_no_filter'|_ }}

{% else %}

- + {{ 'show_the_current_period_and_overview'|_ }} From 7684e966fc8f721d22bb7b015c96d16fc5a74a4f Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:41:31 +0200 Subject: [PATCH 065/668] First code for #616 --- app/Http/Controllers/Admin/LinkController.php | 54 +++++++++++++++++ app/Http/breadcrumbs.php | 8 +++ app/Models/LinkType.php | 38 ++++++++++++ .../2017_08_20_062014_changes_for_v470.php | 58 +++++++++++++++++++ database/seeds/DatabaseSeeder.php | 1 + database/seeds/LinkTypeSeeder.php | 57 ++++++++++++++++++ resources/lang/en_US/firefly.php | 2 + resources/views/admin/index.twig | 3 + resources/views/admin/link/index.twig | 21 +++++++ routes/web.php | 3 + 10 files changed, 245 insertions(+) create mode 100644 app/Http/Controllers/Admin/LinkController.php create mode 100644 app/Models/LinkType.php create mode 100644 database/migrations/2017_08_20_062014_changes_for_v470.php create mode 100644 database/seeds/LinkTypeSeeder.php create mode 100644 resources/views/admin/link/index.twig diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php new file mode 100644 index 0000000000..ca2ce39184 --- /dev/null +++ b/app/Http/Controllers/Admin/LinkController.php @@ -0,0 +1,54 @@ +middleware( + function ($request, $next) { + View::share('title', strval(trans('firefly.administration'))); + View::share('mainTitleIcon', 'fa-hand-spock-o'); + + return $next($request); + } + ); + } + + /** + * + */ + public function index() + { + $subTitle = trans('firefly.journal_link_configuration'); + $subTitleIcon = 'fa-link'; + + return view('admin.link.index', compact('subTitle', 'subTitleIcon')); + } + +} \ No newline at end of file diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index 5c79e234b5..fd3bd27685 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -152,6 +152,14 @@ Breadcrumbs::register( ); +Breadcrumbs::register( + 'admin.links.index', function (BreadCrumbGenerator $breadcrumbs) { + $breadcrumbs->parent('admin.index'); + $breadcrumbs->push(trans('firefly.journal_link_configuration'), route('admin.links.index')); +} +); + + /** * ATTACHMENTS */ diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php new file mode 100644 index 0000000000..8a86779466 --- /dev/null +++ b/app/Models/LinkType.php @@ -0,0 +1,38 @@ + 'date', + 'updated_at' => 'date', + 'deleted_at' => 'date', + 'editable' => 'boolean', + ]; + +} \ No newline at end of file diff --git a/database/migrations/2017_08_20_062014_changes_for_v470.php b/database/migrations/2017_08_20_062014_changes_for_v470.php new file mode 100644 index 0000000000..1fe202c8c1 --- /dev/null +++ b/database/migrations/2017_08_20_062014_changes_for_v470.php @@ -0,0 +1,58 @@ +increments('id'); + $table->timestamps(); + $table->softDeletes(); + $table->string('name'); + $table->string('outward'); + $table->string('inward'); + $table->boolean('editable'); + } + ); + } + + if (!Schema::hasTable('journal_links')) { + Schema::create( + 'journal_links', function (Blueprint $table) { + $table->increments('id'); + $table->integer('link_type_id', false, true); + $table->integer('source_id', false, true); + $table->integer('destination_id', false, true); + $table->text('comment'); + $table->integer('sequence', false, true); + } + ); + } + } +} diff --git a/database/seeds/DatabaseSeeder.php b/database/seeds/DatabaseSeeder.php index 504c129e47..03adb0669e 100644 --- a/database/seeds/DatabaseSeeder.php +++ b/database/seeds/DatabaseSeeder.php @@ -28,6 +28,7 @@ class DatabaseSeeder extends Seeder $this->call(TransactionCurrencySeeder::class); $this->call(TransactionTypeSeeder::class); $this->call(PermissionSeeder::class); + $this->call(LinkTypeSeeder::class); } } diff --git a/database/seeds/LinkTypeSeeder.php b/database/seeds/LinkTypeSeeder.php new file mode 100644 index 0000000000..2101b3f86f --- /dev/null +++ b/database/seeds/LinkTypeSeeder.php @@ -0,0 +1,57 @@ +name = 'Related'; + $link->inward = 'relates to'; + $link->outward = 'is related to'; + $link->editable = false; + $link->save(); + + $link = new LinkType; + $link->name = 'Refund'; + $link->inward = 'refunds'; + $link->outward = 'is refunded by'; + $link->editable = false; + $link->save(); + + $link = new LinkType; + $link->name = 'PartialRefund'; + $link->inward = 'partially refunds'; + $link->outward = 'is partially refunded by'; + $link->editable = false; + $link->save(); + + $link = new LinkType; + $link->name = 'Paid'; + $link->inward = 'pays for'; + $link->outward = 'is paid for by'; + $link->editable = false; + $link->save(); + + } + +} diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index fd23d22f83..a72ba1ccc3 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -935,6 +935,8 @@ return [ 'block_code_bounced' => 'Email message(s) bounced', 'block_code_expired' => 'Demo account expired', 'no_block_code' => 'No reason for block or user not blocked', + // links + 'journal_link_configuration' => 'Transaction links configuration', // split a transaction: diff --git a/resources/views/admin/index.twig b/resources/views/admin/index.twig index 2edd7c681e..1de89abcc3 100644 --- a/resources/views/admin/index.twig +++ b/resources/views/admin/index.twig @@ -13,6 +13,7 @@

@@ -34,4 +35,6 @@
+ + {% endblock %} diff --git a/resources/views/admin/link/index.twig b/resources/views/admin/link/index.twig new file mode 100644 index 0000000000..f5ac34ba4a --- /dev/null +++ b/resources/views/admin/link/index.twig @@ -0,0 +1,21 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists }} +{% endblock %} +{% block content %} +
+
+
+
+

{{ 'journal_link_configuration'|_ }}

+
+
+ +
+
+
+
+ + +{% endblock %} diff --git a/routes/web.php b/routes/web.php index 13aa3714cf..9e380fb15b 100755 --- a/routes/web.php +++ b/routes/web.php @@ -758,6 +758,9 @@ Route::group( Route::get('users/show/{user}', ['uses' => 'UserController@show', 'as' => 'users.show']); Route::post('users/update/{user}', ['uses' => 'UserController@update', 'as' => 'users.update']); + // journal links manager + Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']); + // FF configuration: Route::get('configuration', ['uses' => 'ConfigurationController@index', 'as' => 'configuration.index']); Route::post('configuration', ['uses' => 'ConfigurationController@postIndex', 'as' => 'configuration.index.post']); From 4d595c13803c2a5d8bfa5ac991c31b35d4ad6ba8 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:42:51 +0200 Subject: [PATCH 066/668] More link types for #616 --- database/seeds/LinkTypeSeeder.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/database/seeds/LinkTypeSeeder.php b/database/seeds/LinkTypeSeeder.php index 2101b3f86f..33dd2ff23d 100644 --- a/database/seeds/LinkTypeSeeder.php +++ b/database/seeds/LinkTypeSeeder.php @@ -52,6 +52,20 @@ class LinkTypeSeeder extends Seeder $link->editable = false; $link->save(); + $link = new LinkType; + $link->name = 'Reimbursement'; + $link->inward = 'reimburses'; + $link->outward = 'is reimbursed by'; + $link->editable = false; + $link->save(); + + $link = new LinkType; + $link->name = 'PartialReimbursement'; + $link->inward = 'partially reimburses'; + $link->outward = 'is partially reimbursed by'; + $link->editable = false; + $link->save(); + } } From 6fcbe5a37fd2e091b7e8eeaa64c56d29c3fde0e0 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Aug 2017 12:43:31 +0200 Subject: [PATCH 067/668] Various HTML fixes. --- resources/views/accounts/show.twig | 4 +- resources/views/auth/login.twig | 4 +- resources/views/auth/two-factor.twig | 2 +- resources/views/budgets/income.twig | 4 +- resources/views/budgets/no-budget.twig | 4 +- resources/views/budgets/show.twig | 2 +- resources/views/categories/no-category.twig | 4 +- resources/views/categories/show.twig | 4 +- resources/views/list/journals.twig | 2 +- resources/views/partials/flashes.twig | 8 +- resources/views/partials/password-modal.twig | 2 +- resources/views/piggy-banks/add.twig | 4 +- resources/views/piggy-banks/remove.twig | 4 +- .../views/popup/report/balance-amount.twig | 2 +- .../popup/report/budget-spent-amount.twig | 2 +- .../views/popup/report/category-entry.twig | 2 +- .../views/popup/report/expense-entry.twig | 2 +- .../views/popup/report/income-entry.twig | 2 +- .../views/reports/partials/budget-period.twig | 4 +- .../reports/partials/category-period.twig | 4 +- .../rules/partials/test-trigger-modal.twig | 2 +- resources/views/transactions/index.twig | 4 +- resources/views/transactions/show.twig | 188 +++++++++++++----- .../views/transactions/single/create.twig | 2 +- resources/views/transactions/single/edit.twig | 2 +- resources/views/transactions/split/edit.twig | 2 +- 26 files changed, 178 insertions(+), 88 deletions(-) diff --git a/resources/views/accounts/show.twig b/resources/views/accounts/show.twig index 7249618ff8..a176f0fcd6 100644 --- a/resources/views/accounts/show.twig +++ b/resources/views/accounts/show.twig @@ -88,14 +88,14 @@ {% include 'list.journals' with {sorting:true, hideBills:true, hideBudgets: true, hideCategories: true} %} {% if periods.count > 0 %}

- + {{ 'show_all_no_filter'|_ }}

{% else %}

- + {{ 'show_the_current_period_and_overview'|_ }} diff --git a/resources/views/auth/login.twig b/resources/views/auth/login.twig index 88353375a6..8e925d16ba 100644 --- a/resources/views/auth/login.twig +++ b/resources/views/auth/login.twig @@ -17,7 +17,7 @@

@@ -29,7 +29,7 @@
diff --git a/resources/views/auth/two-factor.twig b/resources/views/auth/two-factor.twig index ea4275aae2..a02c30bf56 100644 --- a/resources/views/auth/two-factor.twig +++ b/resources/views/auth/two-factor.twig @@ -6,7 +6,7 @@
diff --git a/resources/views/budgets/income.twig b/resources/views/budgets/income.twig index 28904dc66d..89e28bd710 100644 --- a/resources/views/budgets/income.twig +++ b/resources/views/budgets/income.twig @@ -1,9 +1,9 @@