From 02687dfe539425e8e5c6de5a510f3ebd0c7fbe60 Mon Sep 17 00:00:00 2001 From: James Cole Date: Sun, 20 Mar 2022 17:11:33 +0100 Subject: [PATCH] Expand search --- .../Extensions/CollectorProperties.php | 43 +- .../Collector/Extensions/MetaCollection.php | 32 +- app/Helpers/Collector/GroupCollector.php | 39 +- .../Collector/GroupCollectorInterface.php | 9 + app/Repositories/Bill/BillRepository.php | 31 + .../Bill/BillRepositoryInterface.php | 16 + app/Repositories/Budget/BudgetRepository.php | 30 + .../Budget/BudgetRepositoryInterface.php | 17 + .../Category/CategoryRepository.php | 26 + .../Category/CategoryRepositoryInterface.php | 16 + app/Repositories/Rule/RuleRepository.php | 2 +- app/Support/Binder/EitherConfigKey.php | 2 +- app/Support/Search/OperatorQuerySearch.php | 167 +++- .../Engine/SearchRuleEngine.php | 4 +- composer.lock | 20 +- config/search.php | 7 + resources/lang/en_US/firefly.php | 754 ++++++++++-------- 17 files changed, 827 insertions(+), 388 deletions(-) diff --git a/app/Helpers/Collector/Extensions/CollectorProperties.php b/app/Helpers/Collector/Extensions/CollectorProperties.php index c3bf6a3396..92b74ab5c3 100644 --- a/app/Helpers/Collector/Extensions/CollectorProperties.php +++ b/app/Helpers/Collector/Extensions/CollectorProperties.php @@ -32,33 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany; */ trait CollectorProperties { - /** @var array The standard fields to select. */ - private $fields; - /** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */ - private $hasAccountInfo; - /** @var bool Will be true if query result includes bill information. */ - private $hasBillInformation; - /** @var bool Will be true if query result contains budget info. */ - private $hasBudgetInformation; - /** @var bool Will be true if query result contains category info. */ - private $hasCatInformation; - /** @var bool Will be true for attachments */ - private $hasJoinedAttTables; + private array $fields; + private bool $hasAccountInfo; + private bool $hasBillInformation; + private bool $hasBudgetInformation; + private bool $hasCatInformation; + private bool $hasJoinedAttTables; private bool $hasJoinedMetaTables; - /** @var bool Will be true of the query has the tag info tables joined. */ - private $hasJoinedTagTables; - /** @var bool */ - private $hasNotesInformation; - /** @var array */ - private $integerFields; - /** @var int The maximum number of results. */ - private $limit; - /** @var int The page to return. */ - private $page; - /** @var HasMany The query object. */ - private $query; - /** @var int Total number of results. */ - private $total; - /** @var User The user object. */ - private $user; + private bool $hasJoinedTagTables; + private bool $hasNotesInformation; + private array $integerFields; + private ?int $limit; + private ?int $page; + private HasMany $query; + private int $total; + private ?User $user; + private array $postFilters; } diff --git a/app/Helpers/Collector/Extensions/MetaCollection.php b/app/Helpers/Collector/Extensions/MetaCollection.php index fef1df8068..4f9c173732 100644 --- a/app/Helpers/Collector/Extensions/MetaCollection.php +++ b/app/Helpers/Collector/Extensions/MetaCollection.php @@ -32,6 +32,7 @@ use FireflyIII\Models\Tag; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; use Illuminate\Support\Collection; +use Log; /** * Trait MetaCollection @@ -295,6 +296,35 @@ trait MetaCollection return $this; } + /** + * Without tags + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface + { + $this->withTagInformation(); + + // this method adds a "postFilter" to the collector. + $list = $tags->pluck('tag')->toArray(); + $filter = function (int $index, array $object) use ($list): bool { + foreach($object['transactions'] as $transaction) { + foreach($transaction['tags'] as $tag) { + if(in_array($tag['name'], $list)) { + return false; + } + } + } + return true; + }; + $this->postFilters[] = $filter; + + + return $this; + } + /** * @return GroupCollectorInterface */ @@ -307,7 +337,7 @@ trait MetaCollection } /** - * Limit results to transactions without a bill.. + * Limit results to transactions without a bill. * * @return GroupCollectorInterface */ diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php index 5e252fcbe5..6ecbde2139 100644 --- a/app/Helpers/Collector/GroupCollector.php +++ b/app/Helpers/Collector/GroupCollector.php @@ -55,6 +55,11 @@ class GroupCollector implements GroupCollectorInterface */ public function __construct() { + $this->postFilters = []; + $this->user = null; + $this->limit = null; + $this->page = null; + $this->hasAccountInfo = false; $this->hasCatInformation = false; $this->hasBudgetInformation = false; @@ -240,7 +245,12 @@ class GroupCollector implements GroupCollectorInterface $result = $this->query->get($this->fields); // now to parse this into an array. - $collection = $this->parseArray($result); + $collection = $this->parseArray($result); + + // filter the array using all available post filters: + $collection = $this->postFilterCollection($collection); + + // count it and continue: $this->total = $collection->count(); // now filter the array according to the page and the limit (if necessary) @@ -730,7 +740,7 @@ class GroupCollector implements GroupCollectorInterface // also merge attachments: if (array_key_exists('attachment_id', $result)) { - $uploaded = 1 === (int)$result['attachment_uploaded']; + $uploaded = 1 === (int) $result['attachment_uploaded']; $attachmentId = (int) $augumentedJournal['attachment_id']; if (0 !== $attachmentId && $uploaded) { $result['attachments'][$attachmentId] = [ @@ -738,6 +748,8 @@ class GroupCollector implements GroupCollectorInterface ]; } } + // unset various fields: + unset($result['tag_id'], $result['tag_name'], $result['tag_date'], $result['tag_description'], $result['tag_latitude'], $result['tag_longitude'], $result['tag_zoom_level']); return $result; } @@ -789,4 +801,27 @@ class GroupCollector implements GroupCollectorInterface return $groups; } + + /** + * @param Collection $collection + * @return Collection + */ + private function postFilterCollection(Collection $collection): Collection + { + Log::debug('Now in postFilterCollection()'); + $newCollection = new Collection; + foreach ($collection as $i => $item) { + Log::debug(sprintf('Now working on item #%d/%d', $i + 1, $collection->count())); + foreach ($this->postFilters as $func) { + if (false === $func($i, $item)) { + // skip other filters, continue to next item. + Log::debug('Filter returns false, jump to next item.'); + continue 2; + } + Log::debug('Filter returns true'); + } + $newCollection->push($item); + } + return $newCollection; + } } diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php index 5216a59242..4f3479f88b 100644 --- a/app/Helpers/Collector/GroupCollectorInterface.php +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -419,6 +419,15 @@ interface GroupCollectorInterface */ public function setTags(Collection $tags): GroupCollectorInterface; + /** + * Only when does not have these tags + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setWithoutSpecificTags(Collection $tags): GroupCollectorInterface; + /** * Limit the search to one specific transaction group. * diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index 188007cd12..c37c858820 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -782,4 +782,35 @@ class BillRepository implements BillRepositoryInterface return $service->update($bill, $data); } + + + /** + * @inheritDoc + */ + public function billEndsWith(string $query, int $limit): Collection + { + $search = $this->user->bills(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s', $query)); + } + $search->orderBy('name', 'ASC') + ->where('active', true); + + return $search->take($limit)->get(); + } + + /** + * @inheritDoc + */ + public function billStartsWith(string $query, int $limit): Collection + { + $search = $this->user->bills(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%s%%', $query)); + } + $search->orderBy('name', 'ASC') + ->where('active', true); + + return $search->take($limit)->get(); + } } diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index 030cb479c1..5d4acb4fe8 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -40,6 +40,22 @@ interface BillRepositoryInterface */ public function correctOrder(): void; + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function billEndsWith(string $query, int $limit): Collection; + + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function billStartsWith(string $query, int $limit): Collection; + /** * @param Bill $bill * diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index 18a3b43135..fc62a77cae 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -564,4 +564,34 @@ class BudgetRepository implements BudgetRepositoryInterface } } } + + /** + * @inheritDoc + */ + public function budgetEndsWith(string $query, int $limit): Collection + { + $search = $this->user->budgets(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s', $query)); + } + $search->orderBy('order', 'ASC') + ->orderBy('name', 'ASC')->where('active', true); + + return $search->take($limit)->get(); + } + + /** + * @inheritDoc + */ + public function budgetStartsWith(string $query, int $limit): Collection + { + $search = $this->user->budgets(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%s%%', $query)); + } + $search->orderBy('order', 'ASC') + ->orderBy('name', 'ASC')->where('active', true); + + return $search->take($limit)->get(); + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index aa963b4a24..35d469c828 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -174,4 +174,21 @@ interface BudgetRepositoryInterface * @return Budget */ public function update(Budget $budget, array $data): Budget; + + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function budgetEndsWith(string $query, int $limit): Collection; + + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function budgetStartsWith(string $query, int $limit): Collection; + } diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index c94a7be944..9d31f1a852 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -420,4 +420,30 @@ class CategoryRepository implements CategoryRepositoryInterface return null; } + + /** + * @inheritDoc + */ + public function categoryEndsWith(string $query, int $limit): Collection + { + $search = $this->user->categories(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s', $query)); + } + + return $search->take($limit)->get(); + } + + /** + * @inheritDoc + */ + public function categoryStartsWith(string $query, int $limit): Collection + { + $search = $this->user->categories(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%s%%', $query)); + } + + return $search->take($limit)->get(); + } } diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index 007153ba61..e633a6544c 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -134,6 +134,22 @@ interface CategoryRepositoryInterface */ public function searchCategory(string $query, int $limit): Collection; + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function categoryEndsWith(string $query, int $limit): Collection; + + /** + * @param string $query + * @param int $limit + * + * @return Collection + */ + public function categoryStartsWith(string $query, int $limit): Collection; + /** * @param User $user */ diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index 1c869ca6f4..b709156db8 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -186,7 +186,7 @@ class RuleRepository implements RuleRepositoryInterface if ('user_action' === $trigger->trigger_type) { continue; } - $needsContext = config(sprintf('firefly.search.operators.%s.needs_context', $trigger->trigger_type)) ?? true; + $needsContext = config(sprintf('search.operators.%s.needs_context', $trigger->trigger_type)) ?? true; if (false === $needsContext) { $params[] = sprintf('%s:true', OperatorQuerySearch::getRootOperator($trigger->trigger_type)); } diff --git a/app/Support/Binder/EitherConfigKey.php b/app/Support/Binder/EitherConfigKey.php index 69bbfa38f3..512fdfa0f2 100644 --- a/app/Support/Binder/EitherConfigKey.php +++ b/app/Support/Binder/EitherConfigKey.php @@ -53,7 +53,7 @@ class EitherConfigKey // triggers and actions: 'firefly.rule-actions', 'firefly.context-rule-actions', - 'firefly.search.operators' + 'search.operators' ]; /** diff --git a/app/Support/Search/OperatorQuerySearch.php b/app/Support/Search/OperatorQuerySearch.php index 18206d9405..0e39c06ce9 100644 --- a/app/Support/Search/OperatorQuerySearch.php +++ b/app/Support/Search/OperatorQuerySearch.php @@ -156,7 +156,7 @@ class OperatorQuerySearch implements SearchInterface $parser = new QueryParser(); try { $query1 = $parser->parse($query); - } catch (TypeError | LogicException $e) { + } catch (TypeError|LogicException $e) { Log::error($e->getMessage()); Log::error(sprintf('Could not parse search: "%s".', $query)); throw new FireflyException('Invalid search value. See the logs.', 0, $e); @@ -233,7 +233,7 @@ class OperatorQuerySearch implements SearchInterface $this->collector->setUser($user); $this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); - $this->setLimit((int)app('preferences')->getForUser($user, 'listPageSize', 50)->data); + $this->setLimit((int) app('preferences')->getForUser($user, 'listPageSize', 50)->data); } @@ -253,7 +253,7 @@ class OperatorQuerySearch implements SearchInterface case Subquery::class: // loop all notes in subquery: foreach ($searchNode->getNodes() as $subNode) { // @phpstan-ignore-line - $this->handleSearchNode($subNode); // let's hope it's not too recursive! + $this->handleSearchNode($subNode); // let's hope it's not too recursive! } break; case Word::class: @@ -265,7 +265,7 @@ class OperatorQuerySearch implements SearchInterface case Emoticon::class: case Emoji::class: case Mention::class: - $allWords = (string)$searchNode->getValue(); + $allWords = (string) $searchNode->getValue(); Log::debug(sprintf('Add words "%s" to search string, because Node class is "%s"', $allWords, $class)); $this->words[] = $allWords; break; @@ -276,11 +276,11 @@ class OperatorQuerySearch implements SearchInterface $operator = strtolower($searchNode->getValue()); $value = $searchNode->getNode()->getValue(); // must be valid operator: - if (in_array($operator, $this->validOperators, true) && $this->updateCollector($operator, (string)$value)) { + if (in_array($operator, $this->validOperators, true) && $this->updateCollector($operator, (string) $value)) { $this->operators->push( [ 'type' => self::getRootOperator($operator), - 'value' => (string)$value, + 'value' => (string) $value, ] ); Log::debug(sprintf('Added operator type "%s"', $operator)); @@ -289,7 +289,7 @@ class OperatorQuerySearch implements SearchInterface Log::debug(sprintf('Added INVALID operator type "%s"', $operator)); $this->invalidOperators[] = [ 'type' => $operator, - 'value' => (string)$value, + 'value' => (string) $value, ]; } } @@ -322,6 +322,30 @@ class OperatorQuerySearch implements SearchInterface // // all account related searches: // + case 'account_is': + $this->searchAccount($value, 3, 4); + break; + case 'account_contains': + $this->searchAccount($value, 3, 3); + break; + case 'account_ends': + $this->searchAccount($value, 3, 2); + break; + case 'account_starts': + $this->searchAccount($value, 3, 1); + break; + case 'account_nr_is': + $this->searchAccountNr($value, 3, 4); + break; + case 'account_nr_contains': + $this->searchAccountNr($value, 3, 3); + break; + case 'account_nr_ends': + $this->searchAccountNr($value, 3, 2); + break; + case 'account_nr_starts': + $this->searchAccountNr($value, 3, 1); + break; case 'source_account_starts': $this->searchAccount($value, 1, 1); break; @@ -347,7 +371,7 @@ class OperatorQuerySearch implements SearchInterface $this->searchAccount($value, 1, 3); break; case 'source_account_id': - $account = $this->accountRepository->find((int)$value); + $account = $this->accountRepository->find((int) $value); if (null !== $account) { $this->collector->setSourceAccounts(new Collection([$account])); } @@ -389,7 +413,7 @@ class OperatorQuerySearch implements SearchInterface $this->searchAccount($value, 2, 3); break; case 'destination_account_id': - $account = $this->accountRepository->find((int)$value); + $account = $this->accountRepository->find((int) $value); if (null !== $account) { $this->collector->setDestinationAccounts(new Collection([$account])); } @@ -401,7 +425,7 @@ class OperatorQuerySearch implements SearchInterface $parts = explode(',', $value); $collection = new Collection; foreach ($parts as $accountId) { - $account = $this->accountRepository->find((int)$accountId); + $account = $this->accountRepository->find((int) $accountId); if (null !== $account) { $collection->push($account); } @@ -481,6 +505,32 @@ class OperatorQuerySearch implements SearchInterface $this->collector->withCategory(); break; case 'category_is': + $category = $this->categoryRepository->findByName($value); + if (null !== $category) { + $this->collector->setCategory($category); + break; + } + $this->collector->findNothing(); + break; + case 'category_ends': + $result = $this->categoryRepository->categoryEndsWith($value, 1337); + if ($result->count() > 0) { + $this->collector->setCategories($result); + } + if (0 === $result->count()) { + $this->collector->findNothing(); + } + break; + case 'category_starts': + $result = $this->categoryRepository->categoryStartsWith($value, 1337); + if ($result->count() > 0) { + $this->collector->setCategories($result); + } + if (0 === $result->count()) { + $this->collector->findNothing(); + } + break; + case 'category_contains': $result = $this->categoryRepository->searchCategory($value, 1337); if ($result->count() > 0) { $this->collector->setCategories($result); @@ -498,7 +548,7 @@ class OperatorQuerySearch implements SearchInterface case 'has_any_budget': $this->collector->withBudget(); break; - case 'budget_is': + case 'budget_contains': $result = $this->budgetRepository->searchBudget($value, 1337); if ($result->count() > 0) { $this->collector->setBudgets($result); @@ -507,6 +557,32 @@ class OperatorQuerySearch implements SearchInterface $this->collector->findNothing(); } break; + case 'budget_is': + $budget = $this->budgetRepository->findByName($value); + if (null !== $budget) { + $this->collector->setBudget($budget); + break; + } + $this->collector->findNothing(); + break; + case 'budget_ends': + $result = $this->budgetRepository->budgetEndsWith($value, 1337); + if ($result->count() > 0) { + $this->collector->setBudgets($result); + } + if (0 === $result->count()) { + $this->collector->findNothing(); + } + break; + case 'budget_starts': + $result = $this->budgetRepository->budgetStartsWith($value, 1337); + if ($result->count() > 0) { + $this->collector->setBudgets($result); + } + if (0 === $result->count()) { + $this->collector->findNothing(); + } + break; // // bill // @@ -516,8 +592,33 @@ class OperatorQuerySearch implements SearchInterface case 'has_any_bill': $this->collector->withBill(); break; - case 'bill_is': + case 'bill_contains': $result = $this->billRepository->searchBill($value, 1337); + if ($result->count() > 0) { + $this->collector->setBills($result); + break; + } + $this->collector->findNothing(); + break; + case 'bill_is': + $bill = $this->billRepository->findByName($value); + if (null !== $bill) { + $this->collector->setBill($bill); + break; + } + $this->collector->findNothing(); + break; + case 'bill_ends': + $result = $this->billRepository->billEndsWith($value, 1337); + if ($result->count() > 0) { + $this->collector->setBills($result); + } + if (0 === $result->count()) { + $this->collector->findNothing(); + } + break; + case 'bill_starts': + $result = $this->billRepository->billStartsWith($value, 1337); if ($result->count() > 0) { $this->collector->setBills($result); } @@ -545,19 +646,25 @@ class OperatorQuerySearch implements SearchInterface $this->collector->findNothing(); } break; + case 'tag_is_not': + $result = $this->tagRepository->searchTag($value); + if ($result->count() > 0) { + $this->collector->setWithoutSpecificTags($result); + } + break; // // notes // - case 'notes_contain': + case 'notes_contains': $this->collector->notesContain($value); break; - case 'notes_start': + case 'notes_starts': $this->collector->notesStartWith($value); break; - case 'notes_end': + case 'notes_ends': $this->collector->notesEndWith($value); break; - case 'notes_are': + case 'notes_is': $this->collector->notesExactly($value); break; case 'no_notes': @@ -569,10 +676,10 @@ class OperatorQuerySearch implements SearchInterface // // amount // - case 'amount_exactly': + case 'amount_is': // strip comma's, make dots. - $value = str_replace(',', '.', (string)$value); + $value = str_replace(',', '.', (string) $value); $amount = app('steam')->positive($value); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); @@ -580,7 +687,7 @@ class OperatorQuerySearch implements SearchInterface break; case 'amount_less': // strip comma's, make dots. - $value = str_replace(',', '.', (string)$value); + $value = str_replace(',', '.', (string) $value); $amount = app('steam')->positive($value); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); @@ -589,7 +696,7 @@ class OperatorQuerySearch implements SearchInterface case 'amount_more': Log::debug(sprintf('Now handling operator "%s"', $operator)); // strip comma's, make dots. - $value = str_replace(',', '.', (string)$value); + $value = str_replace(',', '.', (string) $value); $amount = app('steam')->positive($value); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); $this->collector->amountMore($amount); @@ -657,7 +764,7 @@ class OperatorQuerySearch implements SearchInterface */ public static function getRootOperator(string $operator): string { - $config = config(sprintf('firefly.search.operators.%s', $operator)); + $config = config(sprintf('search.operators.%s', $operator)); if (null === $config) { throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator)); } @@ -672,7 +779,7 @@ class OperatorQuerySearch implements SearchInterface } /** - * searchDirection: 1 = source (default), 2 = destination + * searchDirection: 1 = source (default), 2 = destination, 3 = both * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is * * @param string $value @@ -693,6 +800,11 @@ class OperatorQuerySearch implements SearchInterface $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; $collectorMethod = 'setDestinationAccounts'; } + // either account could be: + if (3 === $searchDirection) { + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; + $collectorMethod = 'setAccounts'; + } // string position (default): starts with: $stringMethod = 'str_starts_with'; @@ -733,7 +845,7 @@ class OperatorQuerySearch implements SearchInterface } /** - * searchDirection: 1 = source (default), 2 = destination + * searchDirection: 1 = source (default), 2 = destination, 3 = both * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is * * @param string $value @@ -754,6 +866,13 @@ class OperatorQuerySearch implements SearchInterface $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; $collectorMethod = 'setDestinationAccounts'; } + + // either account could be: + if (3 === $searchDirection) { + $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE, AccountType::REVENUE]; + $collectorMethod = 'setAccounts'; + } + // string position (default): starts with: $stringMethod = 'str_starts_with'; @@ -782,7 +901,7 @@ class OperatorQuerySearch implements SearchInterface $filtered = $accounts->filter( function (Account $account) use ($value, $stringMethod) { // either IBAN or account number! - $ibanMatch = $stringMethod(strtolower((string)$account->iban), strtolower((string)$value)); + $ibanMatch = $stringMethod(strtolower((string) $account->iban), strtolower((string) $value)); $accountNrMatch = false; /** @var AccountMeta $meta */ foreach ($account->accountMeta as $meta) { diff --git a/app/TransactionRules/Engine/SearchRuleEngine.php b/app/TransactionRules/Engine/SearchRuleEngine.php index 113dedddb7..74957670ed 100644 --- a/app/TransactionRules/Engine/SearchRuleEngine.php +++ b/app/TransactionRules/Engine/SearchRuleEngine.php @@ -186,7 +186,7 @@ class SearchRuleEngine implements RuleEngineInterface } // if needs no context, value is different: - $needsContext = config(sprintf('firefly.search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; + $needsContext = config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; if (false === $needsContext) { Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:true', $ruleTrigger->trigger_type)); $searchArray[$ruleTrigger->trigger_type][] = 'true'; @@ -310,7 +310,7 @@ class SearchRuleEngine implements RuleEngineInterface continue; } $searchArray = []; - $needsContext = config(sprintf('firefly.search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; + $needsContext = config(sprintf('search.operators.%s.needs_context', $ruleTrigger->trigger_type)) ?? true; if (false === $needsContext) { Log::debug(sprintf('SearchRuleEngine:: non strict, will search for: %s:true', $ruleTrigger->trigger_type)); $searchArray[$ruleTrigger->trigger_type] = 'true'; diff --git a/composer.lock b/composer.lock index 6f74ddd2c4..04d0a8a7c6 100644 --- a/composer.lock +++ b/composer.lock @@ -5131,16 +5131,16 @@ }, { "name": "spatie/ignition", - "version": "1.2.4", + "version": "1.2.5", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "ec58c125c15eecaa20180f01ef9667d41a568ba8" + "reference": "982f69f3c2e525cef62fa23ada047d745e4bcda9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/ec58c125c15eecaa20180f01ef9667d41a568ba8", - "reference": "ec58c125c15eecaa20180f01ef9667d41a568ba8", + "url": "https://api.github.com/repos/spatie/ignition/zipball/982f69f3c2e525cef62fa23ada047d745e4bcda9", + "reference": "982f69f3c2e525cef62fa23ada047d745e4bcda9", "shasum": "" }, "require": { @@ -5197,20 +5197,20 @@ "type": "github" } ], - "time": "2022-03-11T13:28:02+00:00" + "time": "2022-03-19T14:07:30+00:00" }, { "name": "spatie/laravel-ignition", - "version": "1.0.10", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "71df77cad94aae4db904aaef1cc2f06950daed76" + "reference": "5b8c360d1f6bcba339a6d593efa02816c06d17c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/71df77cad94aae4db904aaef1cc2f06950daed76", - "reference": "71df77cad94aae4db904aaef1cc2f06950daed76", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/5b8c360d1f6bcba339a6d593efa02816c06d17c3", + "reference": "5b8c360d1f6bcba339a6d593efa02816c06d17c3", "shasum": "" }, "require": { @@ -5284,7 +5284,7 @@ "type": "github" } ], - "time": "2022-03-17T11:01:36+00:00" + "time": "2022-03-19T17:03:56+00:00" }, { "name": "symfony/console", diff --git a/config/search.php b/config/search.php index 93892aa218..c3531afc88 100644 --- a/config/search.php +++ b/config/search.php @@ -31,6 +31,7 @@ return [ 'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true,], 'source_account_starts' => ['alias' => false, 'needs_context' => true,], 'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', 'needs_context' => true,], + 'source_account_nr_is' => ['alias' => false, 'needs_context' => true,], 'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', 'needs_context' => true,], 'source_account_nr_contains' => ['alias' => false, 'needs_context' => true,], @@ -39,6 +40,7 @@ return [ 'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true,], 'source_account_nr_starts' => ['alias' => false, 'needs_context' => true,], 'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true,], + 'destination_account_is' => ['alias' => false, 'needs_context' => true,], 'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true,], 'destination_account_contains' => ['alias' => false, 'needs_context' => true,], @@ -49,6 +51,7 @@ return [ 'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true,], 'destination_account_starts' => ['alias' => false, 'needs_context' => true,], 'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', 'needs_context' => true,], + 'destination_account_nr_is' => ['alias' => false, 'needs_context' => true,], 'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', 'needs_context' => true,], 'destination_account_nr_contains' => ['alias' => false, 'needs_context' => true,], @@ -57,14 +60,17 @@ return [ 'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true,], 'destination_account_nr_starts' => ['alias' => false, 'needs_context' => true,], 'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true,], + 'account_is' => ['alias' => false, 'needs_context' => true,], 'account_contains' => ['alias' => false, 'needs_context' => true,], 'account_ends' => ['alias' => false, 'needs_context' => true,], 'account_starts' => ['alias' => false, 'needs_context' => true,], + 'account_nr_is' => ['alias' => false, 'needs_context' => true,], 'account_nr_contains' => ['alias' => false, 'needs_context' => true,], 'account_nr_ends' => ['alias' => false, 'needs_context' => true,], 'account_nr_starts' => ['alias' => false, 'needs_context' => true,], + 'category_is' => ['alias' => false, 'needs_context' => true,], 'category_contains' => ['alias' => false, 'needs_context' => true,], 'category' => ['alias' => true, 'alias_for' => 'category_contains', 'needs_context' => true,], @@ -80,6 +86,7 @@ return [ 'bill' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true,], 'bill_ends' => ['alias' => false, 'needs_context' => true,], 'bill_starts' => ['alias' => false, 'needs_context' => true,], + // TODO here we are 'external_id_is' => ['alias' => false, 'needs_context' => true,], 'external_id_contains' => ['alias' => false, 'needs_context' => true,], 'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true,], diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 9bdf0679c1..41f8a7cbae 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -24,333 +24,449 @@ declare(strict_types=1); return [ // general stuff: - 'close' => 'Close', - 'actions' => 'Actions', - 'edit' => 'Edit', - 'delete' => 'Delete', - 'split' => 'Split', - 'single_split' => 'Split', - 'clone' => 'Clone', - 'last_seven_days' => 'Last seven days', - 'last_thirty_days' => 'Last thirty days', - 'last_180_days' => 'Last 180 days', - 'YTD' => 'YTD', - 'welcome_back' => 'What\'s playing?', - 'everything' => 'Everything', - 'today' => 'today', - 'customRange' => 'Custom range', - 'date_range' => 'Date range', - 'apply' => 'Apply', - 'select_date' => 'Select date..', - 'cancel' => 'Cancel', - 'from' => 'From', - 'to' => 'To', - 'structure' => 'Structure', - 'help_translating' => 'This help text is not yet available in your language. Will you help translate?', - 'showEverything' => 'Show everything', - 'never' => 'Never', - 'no_results_for_empty_search' => 'Your search was empty, so nothing was found.', - 'removed_amount' => 'Removed :amount', - 'added_amount' => 'Added :amount', - 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', - 'Opening balance' => 'Opening balance', - 'create_new_stuff' => 'Create new stuff', - 'new_withdrawal' => 'New withdrawal', - 'create_new_transaction' => 'Create a new transaction', - 'sidebar_frontpage_create' => 'Create', - 'new_transaction' => 'New transaction', - 'no_rules_for_bill' => 'This bill has no rules associated to it.', - 'go_to_asset_accounts' => 'View your asset accounts', - 'go_to_budgets' => 'Go to your budgets', - 'go_to_withdrawals' => 'Go to your withdrawals', - 'clones_journal_x' => 'This transaction is a clone of ":description" (#:id)', - 'go_to_categories' => 'Go to your categories', - 'go_to_bills' => 'Go to your bills', - 'go_to_expense_accounts' => 'See your expense accounts', - 'go_to_revenue_accounts' => 'See your revenue accounts', - 'go_to_piggies' => 'Go to your piggy banks', - 'new_deposit' => 'New deposit', - 'new_transfer' => 'New transfer', - 'new_transfers' => 'New transfer', - 'new_asset_account' => 'New asset account', - 'new_expense_account' => 'New expense account', - 'new_revenue_account' => 'New revenue account', - 'new_liabilities_account' => 'New liability', - 'new_budget' => 'New budget', - 'new_bill' => 'New bill', - 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', - 'flash_success' => 'Success!', - 'flash_info' => 'Message', - 'flash_warning' => 'Warning!', - 'flash_error' => 'Error!', - 'flash_danger' => 'Danger!', - 'flash_info_multiple' => 'There is one message|There are :count messages', - 'flash_error_multiple' => 'There is one error|There are :count errors', - 'net_worth' => 'Net worth', - 'help_for_this_page' => 'Help for this page', - 'help_for_this_page_body' => 'You can find more information about this page in the documentation.', - 'two_factor_welcome' => 'Hello!', - 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', - 'two_factor_code_here' => 'Enter code here', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - 'two_factor_forgot' => 'I forgot my two-factor thing.', - 'two_factor_lost_header' => 'Lost your two factor authentication?', - 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', - 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, read this entry in the FAQ for instructions.', - 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', - 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', - 'pref_two_factor_new_backup_codes' => 'Get new backup codes', - 'pref_two_factor_backup_code_count' => 'You have :count valid backup code.|You have :count valid backup codes.', - '2fa_i_have_them' => 'I stored them!', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'no_bill_pointer' => 'You seem to have no bills yet. You should create some on the bills-page. Bills can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account|Source accounts', - 'destination_accounts' => 'Destination account|Destination accounts', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', - 'reenable_intro_text' => 'You can also re-enable the introduction guidance.', - 'intro_boxes_after_refresh' => 'The introduction boxes will reappear when you refresh the page.', - 'show_all_no_filter' => 'Show all transactions without grouping them by date.', - 'expenses_by_category' => 'Expenses by category', - 'expenses_by_budget' => 'Expenses by budget', - 'income_by_category' => 'Income by category', - 'expenses_by_asset_account' => 'Expenses by asset account', - 'expenses_by_expense_account' => 'Expenses by expense account', - 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', - 'sum_of_expenses' => 'Sum of expenses', - 'sum_of_income' => 'Sum of income', - 'liabilities' => 'Liabilities', - 'spent_in_specific_budget' => 'Spent in budget ":budget"', - 'spent_in_specific_double' => 'Spent in account ":account"', - 'earned_in_specific_double' => 'Earned in account ":account"', - 'source_account' => 'Source account', - 'source_account_reconciliation' => 'You can\'t edit the source account of a reconciliation transaction.', - 'destination_account' => 'Destination account', - 'destination_account_reconciliation' => 'You can\'t edit the destination account of a reconciliation transaction.', - 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', - 'left_in_budget_limit' => 'Left to spend according to budgeting', - 'current_period' => 'Current period', - 'show_the_current_period_and_overview' => 'Show the current period and overview', - '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 in :currency', - 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end in :currency', - 'chart_budget_in_period_only_currency' => 'The amount you budgeted was in :currency, so this chart will only show transactions in :currency.', - 'chart_account_in_period' => 'Chart for all transactions for account ":name" (:balance) 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"', - 'clone_withdrawal' => 'Clone this withdrawal', - 'clone_deposit' => 'Clone this deposit', - 'clone_transfer' => 'Clone this transfer', - 'multi_select_no_selection' => 'None selected', - 'multi_select_select_all' => 'Select all', - 'multi_select_n_selected' => 'selected', - 'multi_select_all_selected' => 'All selected', - 'multi_select_filter_placeholder' => 'Find..', - 'intro_next_label' => 'Next', - 'intro_prev_label' => 'Previous', - 'intro_skip_label' => 'Skip', - 'intro_done_label' => 'Done', - 'between_dates_breadcrumb' => 'Between :start and :end', - 'all_journals_without_budget' => 'All transactions without a budget', - 'journals_without_budget' => 'Transactions without a budget', - 'all_journals_without_category' => 'All transactions without a category', - 'journals_without_category' => 'Transactions without a category', - 'all_journals_for_account' => 'All transactions for account :name', - 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', - 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', - 'journals_in_period_for_account_js' => 'All transactions for account {title} between {start} and {end}', - 'transferred' => 'Transferred', - 'all_withdrawal' => 'All expenses', - 'all_transactions' => 'All transactions', - 'title_withdrawal_between' => 'All expenses between :start and :end', - 'all_deposit' => 'All revenue', - 'title_deposit_between' => 'All revenue between :start and :end', - 'all_transfers' => 'All transfers', - 'title_transfers_between' => 'All transfers between :start and :end', - 'all_transfer' => 'All transfers', - 'all_journals_for_tag' => 'All transactions for tag ":tag"', - 'title_transfer_between' => 'All transfers between :start and :end', - 'all_journals_for_category' => 'All transactions for category :name', - 'all_journals_for_budget' => 'All transactions for budget :name', - 'chart_all_journals_for_budget' => 'Chart of all transactions for 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', - 'invalid_server_configuration' => 'Invalid server configuration', - 'invalid_locale_settings' => 'Firefly III is unable to format monetary amounts because your server is missing the required packages. There are instructions how to do this.', - 'quickswitch' => 'Quickswitch', - 'sign_in_to_start' => 'Sign in to start your session', - 'sign_in' => 'Sign in', - 'register_new_account' => 'Register a new account', - 'forgot_my_password' => 'I forgot my password', - 'problems_with_input' => 'There were some problems with your input.', - 'reset_password' => 'Reset your password', - 'button_reset_password' => 'Reset password', - 'reset_button' => 'Reset', - 'want_to_login' => 'I want to login', - 'login_page_title' => 'Login to Firefly III', - 'register_page_title' => 'Register at Firefly III', - 'forgot_pw_page_title' => 'Forgot your password for Firefly III', - 'reset_pw_page_title' => 'Reset your password for Firefly III', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demo user.', - 'no_att_demo_user' => 'The demo user can\'t upload attachments.', - 'button_register' => 'Register', - 'authorization' => 'Authorization', - 'active_bills_only' => 'active bills only', - 'active_bills_only_total' => 'all active bills', - 'active_exp_bills_only' => 'active and expected bills only', - 'active_exp_bills_only_total' => 'all active expected bills only', - 'per_period_sum_1D' => 'Expected daily costs', - 'per_period_sum_1W' => 'Expected weekly costs', - 'per_period_sum_1M' => 'Expected monthly costs', - 'per_period_sum_3M' => 'Expected quarterly costs', - 'per_period_sum_6M' => 'Expected half-yearly costs', - 'per_period_sum_1Y' => 'Expected yearly costs', - 'average_per_bill' => 'average per bill', - 'expected_total' => 'expected total', - 'reconciliation_account_name' => ':name reconciliation (:currency)', - 'saved' => 'Saved', - 'advanced_options' => 'Advanced options', - 'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', - 'here_be_dragons' => 'Hic sunt dracones', + 'close' => 'Close', + 'actions' => 'Actions', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'split' => 'Split', + 'single_split' => 'Split', + 'clone' => 'Clone', + 'last_seven_days' => 'Last seven days', + 'last_thirty_days' => 'Last thirty days', + 'last_180_days' => 'Last 180 days', + 'YTD' => 'YTD', + 'welcome_back' => 'What\'s playing?', + 'everything' => 'Everything', + 'today' => 'today', + 'customRange' => 'Custom range', + 'date_range' => 'Date range', + 'apply' => 'Apply', + 'select_date' => 'Select date..', + 'cancel' => 'Cancel', + 'from' => 'From', + 'to' => 'To', + 'structure' => 'Structure', + 'help_translating' => 'This help text is not yet available in your language. Will you help translate?', + 'showEverything' => 'Show everything', + 'never' => 'Never', + 'no_results_for_empty_search' => 'Your search was empty, so nothing was found.', + 'removed_amount' => 'Removed :amount', + 'added_amount' => 'Added :amount', + 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', + 'Opening balance' => 'Opening balance', + 'create_new_stuff' => 'Create new stuff', + 'new_withdrawal' => 'New withdrawal', + 'create_new_transaction' => 'Create a new transaction', + 'sidebar_frontpage_create' => 'Create', + 'new_transaction' => 'New transaction', + 'no_rules_for_bill' => 'This bill has no rules associated to it.', + 'go_to_asset_accounts' => 'View your asset accounts', + 'go_to_budgets' => 'Go to your budgets', + 'go_to_withdrawals' => 'Go to your withdrawals', + 'clones_journal_x' => 'This transaction is a clone of ":description" (#:id)', + 'go_to_categories' => 'Go to your categories', + 'go_to_bills' => 'Go to your bills', + 'go_to_expense_accounts' => 'See your expense accounts', + 'go_to_revenue_accounts' => 'See your revenue accounts', + 'go_to_piggies' => 'Go to your piggy banks', + 'new_deposit' => 'New deposit', + 'new_transfer' => 'New transfer', + 'new_transfers' => 'New transfer', + 'new_asset_account' => 'New asset account', + 'new_expense_account' => 'New expense account', + 'new_revenue_account' => 'New revenue account', + 'new_liabilities_account' => 'New liability', + 'new_budget' => 'New budget', + 'new_bill' => 'New bill', + 'block_account_logout' => 'You have been logged out. Blocked accounts cannot use this site. Did you register with a valid email address?', + 'flash_success' => 'Success!', + 'flash_info' => 'Message', + 'flash_warning' => 'Warning!', + 'flash_error' => 'Error!', + 'flash_danger' => 'Danger!', + 'flash_info_multiple' => 'There is one message|There are :count messages', + 'flash_error_multiple' => 'There is one error|There are :count errors', + 'net_worth' => 'Net worth', + 'help_for_this_page' => 'Help for this page', + 'help_for_this_page_body' => 'You can find more information about this page in the documentation.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', + 'two_factor_code_here' => 'Enter code here', + 'two_factor_title' => 'Two factor authentication', + 'authenticate' => 'Authenticate', + 'two_factor_forgot_title' => 'Lost two factor authentication', + 'two_factor_forgot' => 'I forgot my two-factor thing.', + 'two_factor_lost_header' => 'Lost your two factor authentication?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, read this entry in the FAQ for instructions.', + 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code.|You have :count valid backup codes.', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days days of data may take a while to load.', + 'registered' => 'You have registered successfully!', + 'Default asset account' => 'Default asset account', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'no_bill_pointer' => 'You seem to have no bills yet. You should create some on the bills-page. Bills can help you keep track of expenses.', + 'Savings account' => 'Savings account', + 'Credit card' => 'Credit card', + 'source_accounts' => 'Source account|Source accounts', + 'destination_accounts' => 'Destination account|Destination accounts', + 'user_id_is' => 'Your user id is :user', + 'field_supports_markdown' => 'This field supports Markdown.', + 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', + 'reenable_intro_text' => 'You can also re-enable the introduction guidance.', + 'intro_boxes_after_refresh' => 'The introduction boxes will reappear when you refresh the page.', + 'show_all_no_filter' => 'Show all transactions without grouping them by date.', + 'expenses_by_category' => 'Expenses by category', + 'expenses_by_budget' => 'Expenses by budget', + 'income_by_category' => 'Income by category', + 'expenses_by_asset_account' => 'Expenses by asset account', + 'expenses_by_expense_account' => 'Expenses by expense account', + 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', + 'sum_of_expenses' => 'Sum of expenses', + 'sum_of_income' => 'Sum of income', + 'liabilities' => 'Liabilities', + 'spent_in_specific_budget' => 'Spent in budget ":budget"', + 'spent_in_specific_double' => 'Spent in account ":account"', + 'earned_in_specific_double' => 'Earned in account ":account"', + 'source_account' => 'Source account', + 'source_account_reconciliation' => 'You can\'t edit the source account of a reconciliation transaction.', + 'destination_account' => 'Destination account', + 'destination_account_reconciliation' => 'You can\'t edit the destination account of a reconciliation transaction.', + 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', + 'left_in_budget_limit' => 'Left to spend according to budgeting', + 'current_period' => 'Current period', + 'show_the_current_period_and_overview' => 'Show the current period and overview', + '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 in :currency', + 'chart_budget_in_period' => 'Chart for all transactions for budget ":name" between :start and :end in :currency', + 'chart_budget_in_period_only_currency' => 'The amount you budgeted was in :currency, so this chart will only show transactions in :currency.', + 'chart_account_in_period' => 'Chart for all transactions for account ":name" (:balance) 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"', + 'clone_withdrawal' => 'Clone this withdrawal', + 'clone_deposit' => 'Clone this deposit', + 'clone_transfer' => 'Clone this transfer', + 'multi_select_no_selection' => 'None selected', + 'multi_select_select_all' => 'Select all', + 'multi_select_n_selected' => 'selected', + 'multi_select_all_selected' => 'All selected', + 'multi_select_filter_placeholder' => 'Find..', + 'intro_next_label' => 'Next', + 'intro_prev_label' => 'Previous', + 'intro_skip_label' => 'Skip', + 'intro_done_label' => 'Done', + 'between_dates_breadcrumb' => 'Between :start and :end', + 'all_journals_without_budget' => 'All transactions without a budget', + 'journals_without_budget' => 'Transactions without a budget', + 'all_journals_without_category' => 'All transactions without a category', + 'journals_without_category' => 'Transactions without a category', + 'all_journals_for_account' => 'All transactions for account :name', + 'chart_all_journals_for_account' => 'Chart of all transactions for account :name', + 'journals_in_period_for_account' => 'All transactions for account :name between :start and :end', + 'journals_in_period_for_account_js' => 'All transactions for account {title} between {start} and {end}', + 'transferred' => 'Transferred', + 'all_withdrawal' => 'All expenses', + 'all_transactions' => 'All transactions', + 'title_withdrawal_between' => 'All expenses between :start and :end', + 'all_deposit' => 'All revenue', + 'title_deposit_between' => 'All revenue between :start and :end', + 'all_transfers' => 'All transfers', + 'title_transfers_between' => 'All transfers between :start and :end', + 'all_transfer' => 'All transfers', + 'all_journals_for_tag' => 'All transactions for tag ":tag"', + 'title_transfer_between' => 'All transfers between :start and :end', + 'all_journals_for_category' => 'All transactions for category :name', + 'all_journals_for_budget' => 'All transactions for budget :name', + 'chart_all_journals_for_budget' => 'Chart of all transactions for 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', + 'invalid_server_configuration' => 'Invalid server configuration', + 'invalid_locale_settings' => 'Firefly III is unable to format monetary amounts because your server is missing the required packages. There are instructions how to do this.', + 'quickswitch' => 'Quickswitch', + 'sign_in_to_start' => 'Sign in to start your session', + 'sign_in' => 'Sign in', + 'register_new_account' => 'Register a new account', + 'forgot_my_password' => 'I forgot my password', + 'problems_with_input' => 'There were some problems with your input.', + 'reset_password' => 'Reset your password', + 'button_reset_password' => 'Reset password', + 'reset_button' => 'Reset', + 'want_to_login' => 'I want to login', + 'login_page_title' => 'Login to Firefly III', + 'register_page_title' => 'Register at Firefly III', + 'forgot_pw_page_title' => 'Forgot your password for Firefly III', + 'reset_pw_page_title' => 'Reset your password for Firefly III', + 'cannot_reset_demo_user' => 'You cannot reset the password of the demo user.', + 'no_att_demo_user' => 'The demo user can\'t upload attachments.', + 'button_register' => 'Register', + 'authorization' => 'Authorization', + 'active_bills_only' => 'active bills only', + 'active_bills_only_total' => 'all active bills', + 'active_exp_bills_only' => 'active and expected bills only', + 'active_exp_bills_only_total' => 'all active expected bills only', + 'per_period_sum_1D' => 'Expected daily costs', + 'per_period_sum_1W' => 'Expected weekly costs', + 'per_period_sum_1M' => 'Expected monthly costs', + 'per_period_sum_3M' => 'Expected quarterly costs', + 'per_period_sum_6M' => 'Expected half-yearly costs', + 'per_period_sum_1Y' => 'Expected yearly costs', + 'average_per_bill' => 'average per bill', + 'expected_total' => 'expected total', + 'reconciliation_account_name' => ':name reconciliation (:currency)', + 'saved' => 'Saved', + 'advanced_options' => 'Advanced options', + 'advanced_options_explain' => 'Some pages in Firefly III have advanced options hidden behind this button. This page doesn\'t have anything fancy here, but do check out the others!', + 'here_be_dragons' => 'Hic sunt dracones', // Webhooks - 'webhooks' => 'Webhooks', + 'webhooks' => 'Webhooks', // API access - 'authorization_request' => 'Firefly III v:version Authorization Request', - 'authorization_request_intro' => 'Application ":client" is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', - 'authorization_request_site' => 'You will be redirected to :url which will then be able to access your Firefly III data.', - 'authorization_request_invalid' => 'This access request is invalid. Please never follow this link again.', - 'scopes_will_be_able' => 'This application will be able to:', - 'button_authorize' => 'Authorize', - 'none_in_select_list' => '(none)', - 'no_piggy_bank' => '(no piggy bank)', - 'name_in_currency' => ':name in :currency', - 'paid_in_currency' => 'Paid in :currency', - 'unpaid_in_currency' => 'Unpaid in :currency', - 'is_alpha_warning' => 'You are running an ALPHA version. Be wary of bugs and issues.', - 'is_beta_warning' => 'You are running an BETA version. Be wary of bugs and issues.', - 'all_destination_accounts' => 'Destination accounts', - 'all_source_accounts' => 'Source accounts', - 'back_to_index' => 'Back to the index', - 'cant_logout_guard' => 'Firefly III can\'t log you out.', - 'external_url' => 'External URL', - 'internal_reference' => 'Internal reference', + 'authorization_request' => 'Firefly III v:version Authorization Request', + 'authorization_request_intro' => 'Application ":client" is requesting permission to access your financial administration. Would you like to authorize :client to access these records?', + 'authorization_request_site' => 'You will be redirected to :url which will then be able to access your Firefly III data.', + 'authorization_request_invalid' => 'This access request is invalid. Please never follow this link again.', + 'scopes_will_be_able' => 'This application will be able to:', + 'button_authorize' => 'Authorize', + 'none_in_select_list' => '(none)', + 'no_piggy_bank' => '(no piggy bank)', + 'name_in_currency' => ':name in :currency', + 'paid_in_currency' => 'Paid in :currency', + 'unpaid_in_currency' => 'Unpaid in :currency', + 'is_alpha_warning' => 'You are running an ALPHA version. Be wary of bugs and issues.', + 'is_beta_warning' => 'You are running an BETA version. Be wary of bugs and issues.', + 'all_destination_accounts' => 'Destination accounts', + 'all_source_accounts' => 'Source accounts', + 'back_to_index' => 'Back to the index', + 'cant_logout_guard' => 'Firefly III can\'t log you out.', + 'external_url' => 'External URL', + 'internal_reference' => 'Internal reference', // check for updates: - 'update_check_title' => 'Check for updates', - 'admin_update_check_title' => 'Automatically check for update', - 'admin_update_check_explain' => 'Firefly III can check for updates automatically. When you enable this setting, it will contact the Firefly III update server to see if a new version of Firefly III is available. When it is, you will get a notification. You can test this notification using the button on the right. Please indicate below if you want Firefly III to check for updates.', - 'check_for_updates_permission' => 'Firefly III can check for updates, but it needs your permission to do so. Please go to the administration to indicate if you would like this feature to be enabled.', - 'updates_ask_me_later' => 'Ask me later', - 'updates_do_not_check' => 'Do not check for updates', - 'updates_enable_check' => 'Enable the check for updates', - 'admin_update_check_now_title' => 'Check for updates now', - 'admin_update_check_now_explain' => 'If you press the button, Firefly III will see if your current version is the latest.', - 'check_for_updates_button' => 'Check now!', - 'update_new_version_alert' => 'A new version of Firefly III is available. You are running :your_version, the latest version is :new_version which was released on :date.', - 'update_version_beta' => 'This version is a BETA version. You may run into issues.', - 'update_version_alpha' => 'This version is a ALPHA version. You may run into issues.', - 'update_current_version_alert' => 'You are running :version, which is the latest available release.', - 'update_newer_version_alert' => 'You are running :your_version, which is newer than the latest release, :new_version.', - 'update_check_error' => 'An error occurred while checking for updates: :error', - 'unknown_error' => 'Unknown error. Sorry about that.', - 'just_new_release' => 'A new version is available! Version :version was released :date. This release is very fresh. Wait a few days for the new release to stabilize.', - 'disabled_but_check' => 'You disabled update checking. So don\'t forget to check for updates yourself every now and then. Thank you!', - 'admin_update_channel_title' => 'Update channel', - 'admin_update_channel_explain' => 'Firefly III has three update "channels" which determine how ahead of the curve you are in terms of features, enhancements and bugs. Use the "beta" channel if you\'re adventurous and the "alpha" when you like to live life dangerously.', - 'update_channel_stable' => 'Stable. Everything should work as expected.', - 'update_channel_beta' => 'Beta. New features but things may be broken.', - 'update_channel_alpha' => 'Alpha. We throw stuff in, and use whatever sticks.', + 'update_check_title' => 'Check for updates', + 'admin_update_check_title' => 'Automatically check for update', + 'admin_update_check_explain' => 'Firefly III can check for updates automatically. When you enable this setting, it will contact the Firefly III update server to see if a new version of Firefly III is available. When it is, you will get a notification. You can test this notification using the button on the right. Please indicate below if you want Firefly III to check for updates.', + 'check_for_updates_permission' => 'Firefly III can check for updates, but it needs your permission to do so. Please go to the administration to indicate if you would like this feature to be enabled.', + 'updates_ask_me_later' => 'Ask me later', + 'updates_do_not_check' => 'Do not check for updates', + 'updates_enable_check' => 'Enable the check for updates', + 'admin_update_check_now_title' => 'Check for updates now', + 'admin_update_check_now_explain' => 'If you press the button, Firefly III will see if your current version is the latest.', + 'check_for_updates_button' => 'Check now!', + 'update_new_version_alert' => 'A new version of Firefly III is available. You are running :your_version, the latest version is :new_version which was released on :date.', + 'update_version_beta' => 'This version is a BETA version. You may run into issues.', + 'update_version_alpha' => 'This version is a ALPHA version. You may run into issues.', + 'update_current_version_alert' => 'You are running :version, which is the latest available release.', + 'update_newer_version_alert' => 'You are running :your_version, which is newer than the latest release, :new_version.', + 'update_check_error' => 'An error occurred while checking for updates: :error', + 'unknown_error' => 'Unknown error. Sorry about that.', + 'just_new_release' => 'A new version is available! Version :version was released :date. This release is very fresh. Wait a few days for the new release to stabilize.', + 'disabled_but_check' => 'You disabled update checking. So don\'t forget to check for updates yourself every now and then. Thank you!', + 'admin_update_channel_title' => 'Update channel', + 'admin_update_channel_explain' => 'Firefly III has three update "channels" which determine how ahead of the curve you are in terms of features, enhancements and bugs. Use the "beta" channel if you\'re adventurous and the "alpha" when you like to live life dangerously.', + 'update_channel_stable' => 'Stable. Everything should work as expected.', + 'update_channel_beta' => 'Beta. New features but things may be broken.', + 'update_channel_alpha' => 'Alpha. We throw stuff in, and use whatever sticks.', // search - 'search' => 'Search', - 'search_query' => 'Query', - 'search_found_transactions' => 'Firefly III found :count transaction in :time seconds.|Firefly III found :count transactions in :time seconds.', - 'search_found_more_transactions' => 'Firefly III found more than :count transactions in :time seconds.', - 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', - 'invalid_operators_list' => 'These search parameters are not valid and have been ignored.', - 'search_modifier_date_is' => 'Transaction date is ":value"', - 'search_modifier_id' => 'Transaction ID is ":value"', - 'search_modifier_date_before' => 'Transaction date is before or on ":value"', - 'search_modifier_date_after' => 'Transaction date is after or on ":value"', - 'search_modifier_created_on' => 'Transaction was created on ":value"', - 'search_modifier_updated_on' => 'Transaction was last updated on ":value"', - 'search_modifier_external_id' => 'External ID is ":value"', - 'search_modifier_no_external_url' => 'The transaction has no external URL', - 'search_modifier_any_external_url' => 'The transaction must have a (any) external URL', - 'search_modifier_internal_reference' => 'Internal reference is ":value"', - 'search_modifier_description_starts' => 'Description is ":value"', - 'search_modifier_description_ends' => 'Description ends with ":value"', - 'search_modifier_description_contains' => 'Description contains ":value"', - 'search_modifier_description_is' => 'Description is exactly ":value"', - 'search_modifier_currency_is' => 'Transaction (foreign) currency is ":value"', - 'search_modifier_foreign_currency_is' => 'Transaction foreign currency is ":value"', - 'search_modifier_has_attachments' => 'The transaction must have an attachment', - 'search_modifier_has_no_category' => 'The transaction must have no category', - 'search_modifier_has_any_category' => 'The transaction must have a (any) category', - 'search_modifier_has_no_budget' => 'The transaction must have no budget', - 'search_modifier_has_any_budget' => 'The transaction must have a (any) budget', - 'search_modifier_has_no_bill' => 'The transaction must have no bill', - 'search_modifier_has_any_bill' => 'The transaction must have a (any) bill', - 'search_modifier_has_no_tag' => 'The transaction must have no tags', - 'search_modifier_has_any_tag' => 'The transaction must have a (any) tag', - 'search_modifier_notes_contain' => 'The transaction notes contain ":value"', - 'search_modifier_notes_start' => 'The transaction notes start with ":value"', - 'search_modifier_notes_end' => 'The transaction notes end with ":value"', - 'search_modifier_notes_are' => 'The transaction notes are exactly ":value"', - 'search_modifier_no_notes' => 'The transaction has no notes', - 'search_modifier_any_notes' => 'The transaction must have notes', - 'search_modifier_amount_exactly' => 'Amount is exactly :value', - 'search_modifier_amount_less' => 'Amount is less than or equal to :value', - 'search_modifier_amount_more' => 'Amount is more than or equal to :value', - 'search_modifier_source_account_is' => 'Source account name is exactly ":value"', - 'search_modifier_source_account_contains' => 'Source account name contains ":value"', - 'search_modifier_source_account_starts' => 'Source account name starts with ":value"', - 'search_modifier_source_account_ends' => 'Source account name ends with ":value"', - 'search_modifier_source_account_id' => 'Source account ID is :value', - 'search_modifier_source_account_nr_is' => 'Source account number (IBAN) is ":value"', - 'search_modifier_source_account_nr_contains' => 'Source account number (IBAN) contains ":value"', - 'search_modifier_source_account_nr_starts' => 'Source account number (IBAN) starts with ":value"', - 'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends with ":value"', - 'search_modifier_destination_account_is' => 'Destination account name is exactly ":value"', - 'search_modifier_destination_account_contains' => 'Destination account name contains ":value"', - 'search_modifier_destination_account_starts' => 'Destination account name starts with ":value"', - 'search_modifier_destination_account_ends' => 'Destination account name ends with ":value"', - 'search_modifier_destination_account_id' => 'Destination account ID is :value', - 'search_modifier_destination_is_cash' => 'Destination account is (cash) account', - 'search_modifier_source_is_cash' => 'Source account is (cash) account', - 'search_modifier_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"', - 'search_modifier_destination_account_nr_contains' => 'Destination account number (IBAN) contains ":value"', - 'search_modifier_destination_account_nr_starts' => 'Destination account number (IBAN) starts with ":value"', - 'search_modifier_destination_account_nr_ends' => 'Destination account number (IBAN) ends with ":value"', - 'search_modifier_account_id' => 'Source or destination account ID\'s is/are: :value', - 'search_modifier_category_is' => 'Category is ":value"', - 'search_modifier_budget_is' => 'Budget is ":value"', - 'search_modifier_bill_is' => 'Bill is ":value"', - 'search_modifier_transaction_type' => 'Transaction type is ":value"', - 'search_modifier_tag_is' => 'Tag is ":value"', - 'search_modifier_date_is_year' => 'Transaction is in year ":value"', - 'search_modifier_date_is_month' => 'Transaction is in month ":value"', - 'search_modifier_date_is_day' => 'Transaction is on day of month ":value"', - 'search_modifier_date_before_year' => 'Transaction is before or in year ":value"', - 'search_modifier_date_before_month' => 'Transaction is before or in month ":value"', - 'search_modifier_date_before_day' => 'Transaction is before or on day of month ":value"', - 'search_modifier_date_after_year' => 'Transaction is in or after year ":value"', - 'search_modifier_date_after_month' => 'Transaction is in or after month ":value"', - 'search_modifier_date_after_day' => 'Transaction is after or on day of month ":value"', + 'search' => 'Search', + 'search_query' => 'Query', + 'search_found_transactions' => 'Firefly III found :count transaction in :time seconds.|Firefly III found :count transactions in :time seconds.', + 'search_found_more_transactions' => 'Firefly III found more than :count transactions in :time seconds.', + 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', + 'invalid_operators_list' => 'These search parameters are not valid and have been ignored.', + + // old + + 'search_modifier_date_is' => 'Transaction date is ":value"', + 'search_modifier_id' => 'Transaction ID is ":value"', + 'search_modifier_date_before' => 'Transaction date is before or on ":value"', + 'search_modifier_date_after' => 'Transaction date is after or on ":value"', + 'search_modifier_external_id_is' => 'External ID is ":value"', + 'search_modifier_no_external_url' => 'The transaction has no external URL', + 'search_modifier_any_external_url' => 'The transaction must have a (any) external URL', + 'search_modifier_internal_reference_is' => 'Internal reference is ":value"', + 'search_modifier_description_starts' => 'Description is ":value"', + 'search_modifier_description_ends' => 'Description ends with ":value"', + 'search_modifier_description_contains' => 'Description contains ":value"', + 'search_modifier_description_is' => 'Description is exactly ":value"', + 'search_modifier_currency_is' => 'Transaction (foreign) currency is ":value"', + 'search_modifier_foreign_currency_is' => 'Transaction foreign currency is ":value"', + 'search_modifier_has_attachments' => 'The transaction must have an attachment', + 'search_modifier_has_no_category' => 'The transaction must have no category', + 'search_modifier_has_any_category' => 'The transaction must have a (any) category', + 'search_modifier_has_no_budget' => 'The transaction must have no budget', + 'search_modifier_has_any_budget' => 'The transaction must have a (any) budget', + 'search_modifier_has_no_bill' => 'The transaction must have no bill', + 'search_modifier_has_any_bill' => 'The transaction must have a (any) bill', + 'search_modifier_has_no_tag' => 'The transaction must have no tags', + 'search_modifier_has_any_tag' => 'The transaction must have a (any) tag', + 'search_modifier_notes_contains' => 'The transaction notes contain ":value"', + 'search_modifier_notes_starts' => 'The transaction notes start with ":value"', + 'search_modifier_notes_ends' => 'The transaction notes end with ":value"', + 'search_modifier_notes_is' => 'The transaction notes are exactly ":value"', + 'search_modifier_no_notes' => 'The transaction has no notes', + 'search_modifier_any_notes' => 'The transaction must have notes', + 'search_modifier_amount_is' => 'Amount is exactly :value', + 'search_modifier_amount_less' => 'Amount is less than or equal to :value', + 'search_modifier_amount_more' => 'Amount is more than or equal to :value', + 'search_modifier_source_account_is' => 'Source account name is exactly ":value"', + 'search_modifier_source_account_contains' => 'Source account name contains ":value"', + 'search_modifier_source_account_starts' => 'Source account name starts with ":value"', + 'search_modifier_source_account_ends' => 'Source account name ends with ":value"', + 'search_modifier_source_account_id' => 'Source account ID is :value', + 'search_modifier_source_account_nr_is' => 'Source account number (IBAN) is ":value"', + 'search_modifier_source_account_nr_contains' => 'Source account number (IBAN) contains ":value"', + 'search_modifier_source_account_nr_starts' => 'Source account number (IBAN) starts with ":value"', + 'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends with ":value"', + 'search_modifier_destination_account_is' => 'Destination account name is exactly ":value"', + 'search_modifier_destination_account_contains' => 'Destination account name contains ":value"', + 'search_modifier_destination_account_starts' => 'Destination account name starts with ":value"', + 'search_modifier_destination_account_ends' => 'Destination account name ends with ":value"', + 'search_modifier_destination_account_id' => 'Destination account ID is :value', + 'search_modifier_destination_is_cash' => 'Destination account is (cash) account', + 'search_modifier_source_is_cash' => 'Source account is (cash) account', + 'search_modifier_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"', + 'search_modifier_destination_account_nr_contains' => 'Destination account number (IBAN) contains ":value"', + 'search_modifier_destination_account_nr_starts' => 'Destination account number (IBAN) starts with ":value"', + 'search_modifier_destination_account_nr_ends' => 'Destination account number (IBAN) ends with ":value"', + 'search_modifier_account_id' => 'Source or destination account ID\'s is/are: :value', + 'search_modifier_category_is' => 'Category is ":value"', + 'search_modifier_budget_is' => 'Budget is ":value"', + 'search_modifier_bill_is' => 'Bill is ":value"', + 'search_modifier_transaction_type' => 'Transaction type is ":value"', + 'search_modifier_tag_is' => 'Tag is ":value"', + 'search_modifier_date_on_year' => 'Transaction is in year ":value"', + 'search_modifier_date_on_month' => 'Transaction is in month ":value"', + 'search_modifier_date_on_day' => 'Transaction is on day of month ":value"', + 'search_modifier_date_before_year' => 'Transaction is before or in year ":value"', + 'search_modifier_date_before_month' => 'Transaction is before or in month ":value"', + 'search_modifier_date_before_day' => 'Transaction is before or on day of month ":value"', + 'search_modifier_date_after_year' => 'Transaction is in or after year ":value"', + 'search_modifier_date_after_month' => 'Transaction is in or after month ":value"', + 'search_modifier_date_after_day' => 'Transaction is after or on day of month ":value"', + + + // new + 'search_modifier_tag_is_not' => 'No tag is ":value"', + + 'search_modifier_account_is' => 'Either account is ":value"', + 'search_modifier_account_contains' => 'Either account contains ":value"', + 'search_modifier_account_ends' => 'Either account ends with ":value"', + 'search_modifier_account_starts' => 'Either account starts with ":value"', + 'search_modifier_account_nr_is' => 'Either account number / IBAN is ":value"', + 'search_modifier_account_nr_contains' => 'Either account number / IBAN contains ":value"', + 'search_modifier_account_nr_ends' => 'Either account number / IBAN ends with ":value"', + 'search_modifier_account_nr_starts' => 'Either account number / IBAN starts with ":value"', + 'search_modifier_category_contains' => 'Category contains ":value"', + 'search_modifier_category_ends' => 'Category ends with ":value"', + 'search_modifier_category_starts' => 'Category starts with ":value"', + 'search_modifier_budget_contains' => 'Budget contains ":value"', + 'search_modifier_budget_ends' => 'Budget ends with ":value"', + 'search_modifier_budget_starts' => 'Budget starts with ":value"', + 'search_modifier_bill_contains' => 'Bill contains ":value"', + 'search_modifier_bill_ends' => 'Bill ends with ":value"', + 'search_modifier_bill_starts' => 'Bill starts with ":value"', + 'search_modifier_external_id_contains' => 'External ID contains ":value"', + 'search_modifier_external_id_ends' => 'External ID ends with ":value"', + 'search_modifier_external_id_starts' => 'External ID starts with ":value"', + 'search_modifier_internal_reference_contains' => 'Internal reference contains ":value"', + 'search_modifier_internal_reference_ends' => 'Internal reference ends with ":value"', + 'search_modifier_internal_reference_starts' => 'Internal reference starts with ":value"', + 'search_modifier_external_url_is' => 'External URL is ":value"', + 'search_modifier_external_url_contains' => 'External URL contains ":value"', + 'search_modifier_external_url_ends' => 'External URL ends with ":value"', + 'search_modifier_external_url_starts' => 'External URL starts with ":value"', + 'search_modifier_has_no_attachments' => 'Transaction has no attachments', + 'search_modifier_account_is_cash' => 'Either account is a cash account.', + 'search_modifier_journal_id' => 'The journal ID is ":value"', + 'search_modifier_recurrence_id' => 'The recurring transaction ID is ":value"', + 'search_modifier_foreign_amount_is' => 'The foreign amount is ":value"', + 'search_modifier_foreign_amount_less' => 'The foreign amount is less than ":value"', + 'search_modifier_foreign_amount_more' => 'The foreign amount is more than ":value"', + + // date fields + 'search_modifier_interest_date_on_year' => 'Transaction interest date is in year ":value"', + 'search_modifier_interest_date_on_month' => 'Transaction interest date is in month ":value"', + 'search_modifier_interest_date_on_day' => 'Transaction interest date is on day of month ":value"', + 'search_modifier_interest_date_before_year' => 'Transaction interest date is before or in year ":value"', + 'search_modifier_interest_date_before_month' => 'Transaction interest date is before or in month ":value"', + 'search_modifier_interest_date_before_day' => 'Transaction interest date is before or on day of month ":value"', + 'search_modifier_interest_date_after_year' => 'Transaction interest date is after or in year ":value"', + 'search_modifier_interest_date_after_month' => 'Transaction interest date is after or in month ":value"', + 'search_modifier_interest_date_after_day' => 'Transaction interest date is after or on day of month ":value"', + 'search_modifier_book_date_on_year' => 'Transaction book date is in year ":value"', + 'search_modifier_book_date_on_month' => 'Transaction book date is in month ":value"', + 'search_modifier_book_date_on_day' => 'Transaction book date is on day of month ":value"', + 'search_modifier_book_date_before_year' => 'Transaction book date is before or in year ":value"', + 'search_modifier_book_date_before_month' => 'Transaction book date is before or in month ":value"', + 'search_modifier_book_date_before_day' => 'Transaction book date is before or on day of month ":value"', + 'search_modifier_book_date_after_year' => 'Transaction book date is after or in year ":value"', + 'search_modifier_book_date_after_month' => 'Transaction book date is after or in month ":value"', + 'search_modifier_book_date_after_day' => 'Transaction book date is after or on day of month ":value"', + 'search_modifier_process_date_on_year' => 'Transaction process date is in year ":value"', + 'search_modifier_process_date_on_month' => 'Transaction process date is in month ":value"', + 'search_modifier_process_date_on_day' => 'Transaction process date is on day of month ":value"', + 'search_modifier_process_date_before_year' => 'Transaction process date is before or in year ":value"', + 'search_modifier_process_date_before_month' => 'Transaction process date is before or in month ":value"', + 'search_modifier_process_date_before_day' => 'Transaction process date is before or on day of month ":value"', + 'search_modifier_process_date_after_year' => 'Transaction process date is after or in year ":value"', + 'search_modifier_process_date_after_month' => 'Transaction process date is after or in month ":value"', + 'search_modifier_process_date_after_day' => 'Transaction process date is after or on day of month ":value"', + 'search_modifier_due_date_on_year' => 'Transaction due date is in year ":value"', + 'search_modifier_due_date_on_month' => 'Transaction due date is in month ":value"', + 'search_modifier_due_date_on_day' => 'Transaction due date is on day of month ":value"', + 'search_modifier_due_date_before_year' => 'Transaction due date is before or in year ":value"', + 'search_modifier_due_date_before_month' => 'Transaction due date is before or in month ":value"', + 'search_modifier_due_date_before_day' => 'Transaction due date is before or on day of month ":value"', + 'search_modifier_due_date_after_year' => 'Transaction due date is after or in year ":value"', + 'search_modifier_due_date_after_month' => 'Transaction due date is after or in month ":value"', + 'search_modifier_due_date_after_day' => 'Transaction due date is after or on day of month ":value"', + 'search_modifier_payment_date_on_year' => 'Transaction payment date is in year ":value"', + 'search_modifier_payment_date_on_month' => 'Transaction payment date is in month ":value"', + 'search_modifier_payment_date_on_day' => 'Transaction payment date is on day of month ":value"', + 'search_modifier_payment_date_before_year' => 'Transaction payment date is before or in year ":value"', + 'search_modifier_payment_date_before_month' => 'Transaction payment date is before or in month ":value"', + 'search_modifier_payment_date_before_day' => 'Transaction payment date is before or on day of month ":value"', + 'search_modifier_payment_date_after_year' => 'Transaction payment date is after or in year ":value"', + 'search_modifier_payment_date_after_month' => 'Transaction payment date is after or in month ":value"', + 'search_modifier_payment_date_after_day' => 'Transaction payment date is after or on day of month ":value"', + 'search_modifier_invoice_date_on_year' => 'Transaction invoice date is in year ":value"', + 'search_modifier_invoice_date_on_month' => 'Transaction invoice date is in month ":value"', + 'search_modifier_invoice_date_on_day' => 'Transaction invoice date is on day of month ":value"', + 'search_modifier_invoice_date_before_year' => 'Transaction invoice date is before or in year ":value"', + 'search_modifier_invoice_date_before_month' => 'Transaction invoice date is before or in month ":value"', + 'search_modifier_invoice_date_before_day' => 'Transaction invoice date is before or on day of month ":value"', + 'search_modifier_invoice_date_after_year' => 'Transaction invoice date is after or in year ":value"', + 'search_modifier_invoice_date_after_month' => 'Transaction invoice date is after or in month ":value"', + 'search_modifier_invoice_date_after_day' => 'Transaction invoice date is after or on day of month ":value"', + 'search_modifier_updated_at_on_year' => 'Transaction was last updated in year ":value"', + 'search_modifier_updated_at_on_month' => 'Transaction was last updated in month ":value"', + 'search_modifier_updated_at_on_day' => 'Transaction was last updated on day of month ":value"', + 'search_modifier_updated_at_before_year' => 'Transaction was last updated in or before year ":value"', + 'search_modifier_updated_at_before_month' => 'Transaction was last updated in or before month ":value"', + 'search_modifier_updated_at_before_day' => 'Transaction was last updated on or before day of month ":value"', + 'search_modifier_updated_at_after_year' => 'Transaction was last updated in or after year ":value"', + 'search_modifier_updated_at_after_month' => 'Transaction was last updated in or after month ":value"', + 'search_modifier_updated_at_after_day' => 'Transaction was last updated on or after day of month ":value"', + 'search_modifier_created_at_on_year' => 'Transaction was created in year ":value"', + 'search_modifier_created_at_on_month' => 'Transaction was created in month ":value"', + 'search_modifier_created_at_on_day' => 'Transaction was created on day of month ":value"', + 'search_modifier_created_at_before_year' => 'Transaction was created in or before year ":value"', + 'search_modifier_created_at_before_month' => 'Transaction was created in or before month ":value"', + 'search_modifier_created_at_before_day' => 'Transaction was created on or before day of month ":value"', + 'search_modifier_created_at_after_year' => 'Transaction was created in or after year ":value"', + 'search_modifier_created_at_after_month' => 'Transaction was created in or after month ":value"', + 'search_modifier_created_at_after_day' => 'Transaction was created on or after day of month ":value"', + + 'update_rule_from_query' => 'Update rule ":rule" from search query', 'create_rule_from_query' => 'Create new rule from search query', 'rule_from_search_words' => 'The rule engine has a hard time handling ":string". The suggested rule that fits your search query may give different results. Please verify the rule triggers carefully.',