Expand search

This commit is contained in:
James Cole
2022-03-20 17:11:33 +01:00
parent ba10aa5ca5
commit 02687dfe53
17 changed files with 827 additions and 388 deletions

View File

@@ -32,33 +32,20 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/ */
trait CollectorProperties trait CollectorProperties
{ {
/** @var array The standard fields to select. */ private array $fields;
private $fields; private bool $hasAccountInfo;
/** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */ private bool $hasBillInformation;
private $hasAccountInfo; private bool $hasBudgetInformation;
/** @var bool Will be true if query result includes bill information. */ private bool $hasCatInformation;
private $hasBillInformation; private bool $hasJoinedAttTables;
/** @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 bool $hasJoinedMetaTables; private bool $hasJoinedMetaTables;
/** @var bool Will be true of the query has the tag info tables joined. */ private bool $hasJoinedTagTables;
private $hasJoinedTagTables; private bool $hasNotesInformation;
/** @var bool */ private array $integerFields;
private $hasNotesInformation; private ?int $limit;
/** @var array */ private ?int $page;
private $integerFields; private HasMany $query;
/** @var int The maximum number of results. */ private int $total;
private $limit; private ?User $user;
/** @var int The page to return. */ private array $postFilters;
private $page;
/** @var HasMany The query object. */
private $query;
/** @var int Total number of results. */
private $total;
/** @var User The user object. */
private $user;
} }

View File

@@ -32,6 +32,7 @@ use FireflyIII\Models\Tag;
use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Query\JoinClause; use Illuminate\Database\Query\JoinClause;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log;
/** /**
* Trait MetaCollection * Trait MetaCollection
@@ -295,6 +296,35 @@ trait MetaCollection
return $this; 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 * @return GroupCollectorInterface
*/ */
@@ -307,7 +337,7 @@ trait MetaCollection
} }
/** /**
* Limit results to transactions without a bill.. * Limit results to transactions without a bill.
* *
* @return GroupCollectorInterface * @return GroupCollectorInterface
*/ */

View File

@@ -55,6 +55,11 @@ class GroupCollector implements GroupCollectorInterface
*/ */
public function __construct() public function __construct()
{ {
$this->postFilters = [];
$this->user = null;
$this->limit = null;
$this->page = null;
$this->hasAccountInfo = false; $this->hasAccountInfo = false;
$this->hasCatInformation = false; $this->hasCatInformation = false;
$this->hasBudgetInformation = false; $this->hasBudgetInformation = false;
@@ -240,7 +245,12 @@ class GroupCollector implements GroupCollectorInterface
$result = $this->query->get($this->fields); $result = $this->query->get($this->fields);
// now to parse this into an array. // 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(); $this->total = $collection->count();
// now filter the array according to the page and the limit (if necessary) // now filter the array according to the page and the limit (if necessary)
@@ -730,7 +740,7 @@ class GroupCollector implements GroupCollectorInterface
// also merge attachments: // also merge attachments:
if (array_key_exists('attachment_id', $result)) { if (array_key_exists('attachment_id', $result)) {
$uploaded = 1 === (int)$result['attachment_uploaded']; $uploaded = 1 === (int) $result['attachment_uploaded'];
$attachmentId = (int) $augumentedJournal['attachment_id']; $attachmentId = (int) $augumentedJournal['attachment_id'];
if (0 !== $attachmentId && $uploaded) { if (0 !== $attachmentId && $uploaded) {
$result['attachments'][$attachmentId] = [ $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; return $result;
} }
@@ -789,4 +801,27 @@ class GroupCollector implements GroupCollectorInterface
return $groups; 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;
}
} }

View File

@@ -419,6 +419,15 @@ interface GroupCollectorInterface
*/ */
public function setTags(Collection $tags): 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. * Limit the search to one specific transaction group.
* *

View File

@@ -782,4 +782,35 @@ class BillRepository implements BillRepositoryInterface
return $service->update($bill, $data); 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();
}
} }

View File

@@ -40,6 +40,22 @@ interface BillRepositoryInterface
*/ */
public function correctOrder(): void; 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 * @param Bill $bill
* *

View File

@@ -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();
}
} }

View File

@@ -174,4 +174,21 @@ interface BudgetRepositoryInterface
* @return Budget * @return Budget
*/ */
public function update(Budget $budget, array $data): 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;
} }

View File

@@ -420,4 +420,30 @@ class CategoryRepository implements CategoryRepositoryInterface
return null; 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();
}
} }

View File

@@ -134,6 +134,22 @@ interface CategoryRepositoryInterface
*/ */
public function searchCategory(string $query, int $limit): Collection; 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 * @param User $user
*/ */

View File

@@ -186,7 +186,7 @@ class RuleRepository implements RuleRepositoryInterface
if ('user_action' === $trigger->trigger_type) { if ('user_action' === $trigger->trigger_type) {
continue; 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) { if (false === $needsContext) {
$params[] = sprintf('%s:true', OperatorQuerySearch::getRootOperator($trigger->trigger_type)); $params[] = sprintf('%s:true', OperatorQuerySearch::getRootOperator($trigger->trigger_type));
} }

View File

@@ -53,7 +53,7 @@ class EitherConfigKey
// triggers and actions: // triggers and actions:
'firefly.rule-actions', 'firefly.rule-actions',
'firefly.context-rule-actions', 'firefly.context-rule-actions',
'firefly.search.operators' 'search.operators'
]; ];
/** /**

View File

@@ -156,7 +156,7 @@ class OperatorQuerySearch implements SearchInterface
$parser = new QueryParser(); $parser = new QueryParser();
try { try {
$query1 = $parser->parse($query); $query1 = $parser->parse($query);
} catch (TypeError | LogicException $e) { } catch (TypeError|LogicException $e) {
Log::error($e->getMessage()); Log::error($e->getMessage());
Log::error(sprintf('Could not parse search: "%s".', $query)); Log::error(sprintf('Could not parse search: "%s".', $query));
throw new FireflyException('Invalid search value. See the logs.', 0, $e); 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->setUser($user);
$this->collector->withAccountInformation()->withCategoryInformation()->withBudgetInformation(); $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: case Subquery::class:
// loop all notes in subquery: // loop all notes in subquery:
foreach ($searchNode->getNodes() as $subNode) { // @phpstan-ignore-line 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; break;
case Word::class: case Word::class:
@@ -265,7 +265,7 @@ class OperatorQuerySearch implements SearchInterface
case Emoticon::class: case Emoticon::class:
case Emoji::class: case Emoji::class:
case Mention::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)); Log::debug(sprintf('Add words "%s" to search string, because Node class is "%s"', $allWords, $class));
$this->words[] = $allWords; $this->words[] = $allWords;
break; break;
@@ -276,11 +276,11 @@ class OperatorQuerySearch implements SearchInterface
$operator = strtolower($searchNode->getValue()); $operator = strtolower($searchNode->getValue());
$value = $searchNode->getNode()->getValue(); $value = $searchNode->getNode()->getValue();
// must be valid operator: // 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( $this->operators->push(
[ [
'type' => self::getRootOperator($operator), 'type' => self::getRootOperator($operator),
'value' => (string)$value, 'value' => (string) $value,
] ]
); );
Log::debug(sprintf('Added operator type "%s"', $operator)); 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)); Log::debug(sprintf('Added INVALID operator type "%s"', $operator));
$this->invalidOperators[] = [ $this->invalidOperators[] = [
'type' => $operator, 'type' => $operator,
'value' => (string)$value, 'value' => (string) $value,
]; ];
} }
} }
@@ -322,6 +322,30 @@ class OperatorQuerySearch implements SearchInterface
// //
// all account related searches: // 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': case 'source_account_starts':
$this->searchAccount($value, 1, 1); $this->searchAccount($value, 1, 1);
break; break;
@@ -347,7 +371,7 @@ class OperatorQuerySearch implements SearchInterface
$this->searchAccount($value, 1, 3); $this->searchAccount($value, 1, 3);
break; break;
case 'source_account_id': case 'source_account_id':
$account = $this->accountRepository->find((int)$value); $account = $this->accountRepository->find((int) $value);
if (null !== $account) { if (null !== $account) {
$this->collector->setSourceAccounts(new Collection([$account])); $this->collector->setSourceAccounts(new Collection([$account]));
} }
@@ -389,7 +413,7 @@ class OperatorQuerySearch implements SearchInterface
$this->searchAccount($value, 2, 3); $this->searchAccount($value, 2, 3);
break; break;
case 'destination_account_id': case 'destination_account_id':
$account = $this->accountRepository->find((int)$value); $account = $this->accountRepository->find((int) $value);
if (null !== $account) { if (null !== $account) {
$this->collector->setDestinationAccounts(new Collection([$account])); $this->collector->setDestinationAccounts(new Collection([$account]));
} }
@@ -401,7 +425,7 @@ class OperatorQuerySearch implements SearchInterface
$parts = explode(',', $value); $parts = explode(',', $value);
$collection = new Collection; $collection = new Collection;
foreach ($parts as $accountId) { foreach ($parts as $accountId) {
$account = $this->accountRepository->find((int)$accountId); $account = $this->accountRepository->find((int) $accountId);
if (null !== $account) { if (null !== $account) {
$collection->push($account); $collection->push($account);
} }
@@ -481,6 +505,32 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->withCategory(); $this->collector->withCategory();
break; break;
case 'category_is': 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); $result = $this->categoryRepository->searchCategory($value, 1337);
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setCategories($result); $this->collector->setCategories($result);
@@ -498,7 +548,7 @@ class OperatorQuerySearch implements SearchInterface
case 'has_any_budget': case 'has_any_budget':
$this->collector->withBudget(); $this->collector->withBudget();
break; break;
case 'budget_is': case 'budget_contains':
$result = $this->budgetRepository->searchBudget($value, 1337); $result = $this->budgetRepository->searchBudget($value, 1337);
if ($result->count() > 0) { if ($result->count() > 0) {
$this->collector->setBudgets($result); $this->collector->setBudgets($result);
@@ -507,6 +557,32 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing(); $this->collector->findNothing();
} }
break; 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 // bill
// //
@@ -516,8 +592,33 @@ class OperatorQuerySearch implements SearchInterface
case 'has_any_bill': case 'has_any_bill':
$this->collector->withBill(); $this->collector->withBill();
break; break;
case 'bill_is': case 'bill_contains':
$result = $this->billRepository->searchBill($value, 1337); $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) { if ($result->count() > 0) {
$this->collector->setBills($result); $this->collector->setBills($result);
} }
@@ -545,19 +646,25 @@ class OperatorQuerySearch implements SearchInterface
$this->collector->findNothing(); $this->collector->findNothing();
} }
break; break;
case 'tag_is_not':
$result = $this->tagRepository->searchTag($value);
if ($result->count() > 0) {
$this->collector->setWithoutSpecificTags($result);
}
break;
// //
// notes // notes
// //
case 'notes_contain': case 'notes_contains':
$this->collector->notesContain($value); $this->collector->notesContain($value);
break; break;
case 'notes_start': case 'notes_starts':
$this->collector->notesStartWith($value); $this->collector->notesStartWith($value);
break; break;
case 'notes_end': case 'notes_ends':
$this->collector->notesEndWith($value); $this->collector->notesEndWith($value);
break; break;
case 'notes_are': case 'notes_is':
$this->collector->notesExactly($value); $this->collector->notesExactly($value);
break; break;
case 'no_notes': case 'no_notes':
@@ -569,10 +676,10 @@ class OperatorQuerySearch implements SearchInterface
// //
// amount // amount
// //
case 'amount_exactly': case 'amount_is':
// strip comma's, make dots. // strip comma's, make dots.
$value = str_replace(',', '.', (string)$value); $value = str_replace(',', '.', (string) $value);
$amount = app('steam')->positive($value); $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
@@ -580,7 +687,7 @@ class OperatorQuerySearch implements SearchInterface
break; break;
case 'amount_less': case 'amount_less':
// strip comma's, make dots. // strip comma's, make dots.
$value = str_replace(',', '.', (string)$value); $value = str_replace(',', '.', (string) $value);
$amount = app('steam')->positive($value); $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
@@ -589,7 +696,7 @@ class OperatorQuerySearch implements SearchInterface
case 'amount_more': case 'amount_more':
Log::debug(sprintf('Now handling operator "%s"', $operator)); Log::debug(sprintf('Now handling operator "%s"', $operator));
// strip comma's, make dots. // strip comma's, make dots.
$value = str_replace(',', '.', (string)$value); $value = str_replace(',', '.', (string) $value);
$amount = app('steam')->positive($value); $amount = app('steam')->positive($value);
Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount)); Log::debug(sprintf('Set "%s" using collector with value "%s"', $operator, $amount));
$this->collector->amountMore($amount); $this->collector->amountMore($amount);
@@ -657,7 +764,7 @@ class OperatorQuerySearch implements SearchInterface
*/ */
public static function getRootOperator(string $operator): string 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) { if (null === $config) {
throw new FireflyException(sprintf('No configuration for search operator "%s"', $operator)); 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 * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
* *
* @param string $value * @param string $value
@@ -693,6 +800,11 @@ class OperatorQuerySearch implements SearchInterface
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
$collectorMethod = 'setDestinationAccounts'; $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: // string position (default): starts with:
$stringMethod = 'str_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 * stringPosition: 1 = start (default), 2 = end, 3 = contains, 4 = is
* *
* @param string $value * @param string $value
@@ -754,6 +866,13 @@ class OperatorQuerySearch implements SearchInterface
$searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
$collectorMethod = 'setDestinationAccounts'; $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: // string position (default): starts with:
$stringMethod = 'str_starts_with'; $stringMethod = 'str_starts_with';
@@ -782,7 +901,7 @@ class OperatorQuerySearch implements SearchInterface
$filtered = $accounts->filter( $filtered = $accounts->filter(
function (Account $account) use ($value, $stringMethod) { function (Account $account) use ($value, $stringMethod) {
// either IBAN or account number! // 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; $accountNrMatch = false;
/** @var AccountMeta $meta */ /** @var AccountMeta $meta */
foreach ($account->accountMeta as $meta) { foreach ($account->accountMeta as $meta) {

View File

@@ -186,7 +186,7 @@ class SearchRuleEngine implements RuleEngineInterface
} }
// if needs no context, value is different: // 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) { if (false === $needsContext) {
Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:true', $ruleTrigger->trigger_type)); Log::debug(sprintf('SearchRuleEngine:: add a rule trigger: %s:true', $ruleTrigger->trigger_type));
$searchArray[$ruleTrigger->trigger_type][] = 'true'; $searchArray[$ruleTrigger->trigger_type][] = 'true';
@@ -310,7 +310,7 @@ class SearchRuleEngine implements RuleEngineInterface
continue; continue;
} }
$searchArray = []; $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) { if (false === $needsContext) {
Log::debug(sprintf('SearchRuleEngine:: non strict, will search for: %s:true', $ruleTrigger->trigger_type)); Log::debug(sprintf('SearchRuleEngine:: non strict, will search for: %s:true', $ruleTrigger->trigger_type));
$searchArray[$ruleTrigger->trigger_type] = 'true'; $searchArray[$ruleTrigger->trigger_type] = 'true';

20
composer.lock generated
View File

@@ -5131,16 +5131,16 @@
}, },
{ {
"name": "spatie/ignition", "name": "spatie/ignition",
"version": "1.2.4", "version": "1.2.5",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/ignition.git", "url": "https://github.com/spatie/ignition.git",
"reference": "ec58c125c15eecaa20180f01ef9667d41a568ba8" "reference": "982f69f3c2e525cef62fa23ada047d745e4bcda9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/ignition/zipball/ec58c125c15eecaa20180f01ef9667d41a568ba8", "url": "https://api.github.com/repos/spatie/ignition/zipball/982f69f3c2e525cef62fa23ada047d745e4bcda9",
"reference": "ec58c125c15eecaa20180f01ef9667d41a568ba8", "reference": "982f69f3c2e525cef62fa23ada047d745e4bcda9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -5197,20 +5197,20 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-11T13:28:02+00:00" "time": "2022-03-19T14:07:30+00:00"
}, },
{ {
"name": "spatie/laravel-ignition", "name": "spatie/laravel-ignition",
"version": "1.0.10", "version": "1.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/laravel-ignition.git", "url": "https://github.com/spatie/laravel-ignition.git",
"reference": "71df77cad94aae4db904aaef1cc2f06950daed76" "reference": "5b8c360d1f6bcba339a6d593efa02816c06d17c3"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/71df77cad94aae4db904aaef1cc2f06950daed76", "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/5b8c360d1f6bcba339a6d593efa02816c06d17c3",
"reference": "71df77cad94aae4db904aaef1cc2f06950daed76", "reference": "5b8c360d1f6bcba339a6d593efa02816c06d17c3",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@@ -5284,7 +5284,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2022-03-17T11:01:36+00:00" "time": "2022-03-19T17:03:56+00:00"
}, },
{ {
"name": "symfony/console", "name": "symfony/console",

View File

@@ -31,6 +31,7 @@ return [
'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true,], 'from_account_ends' => ['alias' => true, 'alias_for' => 'source_account_ends', 'needs_context' => true,],
'source_account_starts' => ['alias' => false, 'needs_context' => true,], 'source_account_starts' => ['alias' => false, 'needs_context' => true,],
'from_account_starts' => ['alias' => true, 'alias_for' => 'source_account_starts', '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,], 'source_account_nr_is' => ['alias' => false, 'needs_context' => true,],
'from_account_nr_is' => ['alias' => true, 'alias_for' => 'source_account_nr_is', '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,], '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,], 'from_account_nr_ends' => ['alias' => true, 'alias_for' => 'source_account_nr_ends', 'needs_context' => true,],
'source_account_nr_starts' => ['alias' => false, '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,], 'from_account_nr_starts' => ['alias' => true, 'alias_for' => 'source_account_nr_starts', 'needs_context' => true,],
'destination_account_is' => ['alias' => false, 'needs_context' => true,], 'destination_account_is' => ['alias' => false, 'needs_context' => true,],
'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true,], 'to_account_is' => ['alias' => true, 'alias_for' => 'destination_account_is', 'needs_context' => true,],
'destination_account_contains' => ['alias' => false, '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,], 'to_account_ends' => ['alias' => true, 'alias_for' => 'destination_account_ends', 'needs_context' => true,],
'destination_account_starts' => ['alias' => false, 'needs_context' => true,], 'destination_account_starts' => ['alias' => false, 'needs_context' => true,],
'to_account_starts' => ['alias' => true, 'alias_for' => 'destination_account_starts', '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,], 'destination_account_nr_is' => ['alias' => false, 'needs_context' => true,],
'to_account_nr_is' => ['alias' => true, 'alias_for' => 'destination_account_nr_is', '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,], '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,], 'to_account_nr_ends' => ['alias' => true, 'alias_for' => 'destination_account_nr_ends', 'needs_context' => true,],
'destination_account_nr_starts' => ['alias' => false, '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,], 'to_account_nr_starts' => ['alias' => true, 'alias_for' => 'destination_account_nr_starts', 'needs_context' => true,],
'account_is' => ['alias' => false, 'needs_context' => true,], 'account_is' => ['alias' => false, 'needs_context' => true,],
'account_contains' => ['alias' => false, 'needs_context' => true,], 'account_contains' => ['alias' => false, 'needs_context' => true,],
'account_ends' => ['alias' => false, 'needs_context' => true,], 'account_ends' => ['alias' => false, 'needs_context' => true,],
'account_starts' => ['alias' => false, 'needs_context' => true,], 'account_starts' => ['alias' => false, 'needs_context' => true,],
'account_nr_is' => ['alias' => false, 'needs_context' => true,], 'account_nr_is' => ['alias' => false, 'needs_context' => true,],
'account_nr_contains' => ['alias' => false, 'needs_context' => true,], 'account_nr_contains' => ['alias' => false, 'needs_context' => true,],
'account_nr_ends' => ['alias' => false, 'needs_context' => true,], 'account_nr_ends' => ['alias' => false, 'needs_context' => true,],
'account_nr_starts' => ['alias' => false, 'needs_context' => true,], 'account_nr_starts' => ['alias' => false, 'needs_context' => true,],
'category_is' => ['alias' => false, 'needs_context' => true,], 'category_is' => ['alias' => false, 'needs_context' => true,],
'category_contains' => ['alias' => false, 'needs_context' => true,], 'category_contains' => ['alias' => false, 'needs_context' => true,],
'category' => ['alias' => true, 'alias_for' => 'category_contains', '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' => ['alias' => true, 'alias_for' => 'bill_contains', 'needs_context' => true,],
'bill_ends' => ['alias' => false, 'needs_context' => true,], 'bill_ends' => ['alias' => false, 'needs_context' => true,],
'bill_starts' => ['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_is' => ['alias' => false, 'needs_context' => true,],
'external_id_contains' => ['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,], 'external_id' => ['alias' => true, 'alias_for' => 'external_id_contains', 'needs_context' => true,],

View File

@@ -24,333 +24,449 @@ declare(strict_types=1);
return [ return [
// general stuff: // general stuff:
'close' => 'Close', 'close' => 'Close',
'actions' => 'Actions', 'actions' => 'Actions',
'edit' => 'Edit', 'edit' => 'Edit',
'delete' => 'Delete', 'delete' => 'Delete',
'split' => 'Split', 'split' => 'Split',
'single_split' => 'Split', 'single_split' => 'Split',
'clone' => 'Clone', 'clone' => 'Clone',
'last_seven_days' => 'Last seven days', 'last_seven_days' => 'Last seven days',
'last_thirty_days' => 'Last thirty days', 'last_thirty_days' => 'Last thirty days',
'last_180_days' => 'Last 180 days', 'last_180_days' => 'Last 180 days',
'YTD' => 'YTD', 'YTD' => 'YTD',
'welcome_back' => 'What\'s playing?', 'welcome_back' => 'What\'s playing?',
'everything' => 'Everything', 'everything' => 'Everything',
'today' => 'today', 'today' => 'today',
'customRange' => 'Custom range', 'customRange' => 'Custom range',
'date_range' => 'Date range', 'date_range' => 'Date range',
'apply' => 'Apply', 'apply' => 'Apply',
'select_date' => 'Select date..', 'select_date' => 'Select date..',
'cancel' => 'Cancel', 'cancel' => 'Cancel',
'from' => 'From', 'from' => 'From',
'to' => 'To', 'to' => 'To',
'structure' => 'Structure', 'structure' => 'Structure',
'help_translating' => 'This help text is not yet available in your language. <a href="https://crowdin.com/project/firefly-iii-help">Will you help translate?</a>', 'help_translating' => 'This help text is not yet available in your language. <a href="https://crowdin.com/project/firefly-iii-help">Will you help translate?</a>',
'showEverything' => 'Show everything', 'showEverything' => 'Show everything',
'never' => 'Never', 'never' => 'Never',
'no_results_for_empty_search' => 'Your search was empty, so nothing was found.', 'no_results_for_empty_search' => 'Your search was empty, so nothing was found.',
'removed_amount' => 'Removed :amount', 'removed_amount' => 'Removed :amount',
'added_amount' => 'Added :amount', 'added_amount' => 'Added :amount',
'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.', 'asset_account_role_help' => 'Any extra options resulting from your choice can be set later.',
'Opening balance' => 'Opening balance', 'Opening balance' => 'Opening balance',
'create_new_stuff' => 'Create new stuff', 'create_new_stuff' => 'Create new stuff',
'new_withdrawal' => 'New withdrawal', 'new_withdrawal' => 'New withdrawal',
'create_new_transaction' => 'Create a new transaction', 'create_new_transaction' => 'Create a new transaction',
'sidebar_frontpage_create' => 'Create', 'sidebar_frontpage_create' => 'Create',
'new_transaction' => 'New transaction', 'new_transaction' => 'New transaction',
'no_rules_for_bill' => 'This bill has no rules associated to it.', 'no_rules_for_bill' => 'This bill has no rules associated to it.',
'go_to_asset_accounts' => 'View your asset accounts', 'go_to_asset_accounts' => 'View your asset accounts',
'go_to_budgets' => 'Go to your budgets', 'go_to_budgets' => 'Go to your budgets',
'go_to_withdrawals' => 'Go to your withdrawals', 'go_to_withdrawals' => 'Go to your withdrawals',
'clones_journal_x' => 'This transaction is a clone of ":description" (#:id)', 'clones_journal_x' => 'This transaction is a clone of ":description" (#:id)',
'go_to_categories' => 'Go to your categories', 'go_to_categories' => 'Go to your categories',
'go_to_bills' => 'Go to your bills', 'go_to_bills' => 'Go to your bills',
'go_to_expense_accounts' => 'See your expense accounts', 'go_to_expense_accounts' => 'See your expense accounts',
'go_to_revenue_accounts' => 'See your revenue accounts', 'go_to_revenue_accounts' => 'See your revenue accounts',
'go_to_piggies' => 'Go to your piggy banks', 'go_to_piggies' => 'Go to your piggy banks',
'new_deposit' => 'New deposit', 'new_deposit' => 'New deposit',
'new_transfer' => 'New transfer', 'new_transfer' => 'New transfer',
'new_transfers' => 'New transfer', 'new_transfers' => 'New transfer',
'new_asset_account' => 'New asset account', 'new_asset_account' => 'New asset account',
'new_expense_account' => 'New expense account', 'new_expense_account' => 'New expense account',
'new_revenue_account' => 'New revenue account', 'new_revenue_account' => 'New revenue account',
'new_liabilities_account' => 'New liability', 'new_liabilities_account' => 'New liability',
'new_budget' => 'New budget', 'new_budget' => 'New budget',
'new_bill' => 'New bill', '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?', '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_success' => 'Success!',
'flash_info' => 'Message', 'flash_info' => 'Message',
'flash_warning' => 'Warning!', 'flash_warning' => 'Warning!',
'flash_error' => 'Error!', 'flash_error' => 'Error!',
'flash_danger' => 'Danger!', 'flash_danger' => 'Danger!',
'flash_info_multiple' => 'There is one message|There are :count messages', 'flash_info_multiple' => 'There is one message|There are :count messages',
'flash_error_multiple' => 'There is one error|There are :count errors', 'flash_error_multiple' => 'There is one error|There are :count errors',
'net_worth' => 'Net worth', 'net_worth' => 'Net worth',
'help_for_this_page' => 'Help for this page', 'help_for_this_page' => 'Help for this page',
'help_for_this_page_body' => 'You can find more information about this page <a href="https://docs.firefly-iii.org/">in the documentation</a>.', 'help_for_this_page_body' => 'You can find more information about this page <a href="https://docs.firefly-iii.org/">in the documentation</a>.',
'two_factor_welcome' => 'Hello!', '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_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_code_here' => 'Enter code here',
'two_factor_title' => 'Two factor authentication', 'two_factor_title' => 'Two factor authentication',
'authenticate' => 'Authenticate', 'authenticate' => 'Authenticate',
'two_factor_forgot_title' => 'Lost two factor authentication', 'two_factor_forgot_title' => 'Lost two factor authentication',
'two_factor_forgot' => 'I forgot my two-factor thing.', 'two_factor_forgot' => 'I forgot my two-factor thing.',
'two_factor_lost_header' => 'Lost your two factor authentication?', '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_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 <a href="https://docs.firefly-iii.org/faq/other#i-lost-my-two-factor-authentication-codes-and-backup-codes">this entry in the FAQ</a> for instructions.', 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, read <a href="https://docs.firefly-iii.org/faq/other#i-lost-my-two-factor-authentication-codes-and-backup-codes">this entry in the FAQ</a> for instructions.',
'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, <a href="mailto::site_owner">:site_owner</a> and ask them to reset your two factor authentication.', 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, <a href="mailto::site_owner">:site_owner</a> 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.', '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_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.', '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!', '2fa_i_have_them' => 'I stored them!',
'warning_much_data' => ':days days of data may take a while to load.', 'warning_much_data' => ':days days of data may take a while to load.',
'registered' => 'You have registered successfully!', 'registered' => 'You have registered successfully!',
'Default asset account' => 'Default asset account', 'Default asset account' => 'Default asset account',
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the <a href="budgets">budgets</a>-page. Budgets can help you keep track of expenses.', 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the <a href="budgets">budgets</a>-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 <a href="bills">bills</a>-page. Bills can help you keep track of expenses.', 'no_bill_pointer' => 'You seem to have no bills yet. You should create some on the <a href="bills">bills</a>-page. Bills can help you keep track of expenses.',
'Savings account' => 'Savings account', 'Savings account' => 'Savings account',
'Credit card' => 'Credit card', 'Credit card' => 'Credit card',
'source_accounts' => 'Source account|Source accounts', 'source_accounts' => 'Source account|Source accounts',
'destination_accounts' => 'Destination account|Destination accounts', 'destination_accounts' => 'Destination account|Destination accounts',
'user_id_is' => 'Your user id is <strong>:user</strong>', 'user_id_is' => 'Your user id is <strong>:user</strong>',
'field_supports_markdown' => 'This field supports <a href="https://en.support.wordpress.com/markdown-quick-reference/">Markdown</a>.', 'field_supports_markdown' => 'This field supports <a href="https://en.support.wordpress.com/markdown-quick-reference/">Markdown</a>.',
'need_more_help' => 'If you need more help using Firefly III, please <a href="https://github.com/firefly-iii/firefly-iii/issues">open a ticket on Github</a>.', 'need_more_help' => 'If you need more help using Firefly III, please <a href="https://github.com/firefly-iii/firefly-iii/issues">open a ticket on Github</a>.',
'reenable_intro_text' => 'You can also re-enable <a href="#" id="reenableGuidance">the introduction guidance</a>.', 'reenable_intro_text' => 'You can also re-enable <a href="#" id="reenableGuidance">the introduction guidance</a>.',
'intro_boxes_after_refresh' => 'The introduction boxes will reappear when you refresh the page.', '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.', 'show_all_no_filter' => 'Show all transactions without grouping them by date.',
'expenses_by_category' => 'Expenses by category', 'expenses_by_category' => 'Expenses by category',
'expenses_by_budget' => 'Expenses by budget', 'expenses_by_budget' => 'Expenses by budget',
'income_by_category' => 'Income by category', 'income_by_category' => 'Income by category',
'expenses_by_asset_account' => 'Expenses by asset account', 'expenses_by_asset_account' => 'Expenses by asset account',
'expenses_by_expense_account' => 'Expenses by expense account', 'expenses_by_expense_account' => 'Expenses by expense account',
'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.', 'cannot_redirect_to_account' => 'Firefly III cannot redirect you to the correct page. Apologies.',
'sum_of_expenses' => 'Sum of expenses', 'sum_of_expenses' => 'Sum of expenses',
'sum_of_income' => 'Sum of income', 'sum_of_income' => 'Sum of income',
'liabilities' => 'Liabilities', 'liabilities' => 'Liabilities',
'spent_in_specific_budget' => 'Spent in budget ":budget"', 'spent_in_specific_budget' => 'Spent in budget ":budget"',
'spent_in_specific_double' => 'Spent in account ":account"', 'spent_in_specific_double' => 'Spent in account ":account"',
'earned_in_specific_double' => 'Earned in account ":account"', 'earned_in_specific_double' => 'Earned in account ":account"',
'source_account' => 'Source account', 'source_account' => 'Source account',
'source_account_reconciliation' => 'You can\'t edit the source account of a reconciliation transaction.', 'source_account_reconciliation' => 'You can\'t edit the source account of a reconciliation transaction.',
'destination_account' => 'Destination account', 'destination_account' => 'Destination account',
'destination_account_reconciliation' => 'You can\'t edit the destination account of a reconciliation transaction.', 'destination_account_reconciliation' => 'You can\'t edit the destination account of a reconciliation transaction.',
'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"', 'sum_of_expenses_in_budget' => 'Spent total in budget ":budget"',
'left_in_budget_limit' => 'Left to spend according to budgeting', 'left_in_budget_limit' => 'Left to spend according to budgeting',
'current_period' => 'Current period', 'current_period' => 'Current period',
'show_the_current_period_and_overview' => 'Show the current period and overview', '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.', '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', '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' => '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_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_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_in_period' => 'Chart for all transactions for category ":name" between :start and :end',
'chart_category_all' => 'Chart for all transactions for category ":name"', 'chart_category_all' => 'Chart for all transactions for category ":name"',
'clone_withdrawal' => 'Clone this withdrawal', 'clone_withdrawal' => 'Clone this withdrawal',
'clone_deposit' => 'Clone this deposit', 'clone_deposit' => 'Clone this deposit',
'clone_transfer' => 'Clone this transfer', 'clone_transfer' => 'Clone this transfer',
'multi_select_no_selection' => 'None selected', 'multi_select_no_selection' => 'None selected',
'multi_select_select_all' => 'Select all', 'multi_select_select_all' => 'Select all',
'multi_select_n_selected' => 'selected', 'multi_select_n_selected' => 'selected',
'multi_select_all_selected' => 'All selected', 'multi_select_all_selected' => 'All selected',
'multi_select_filter_placeholder' => 'Find..', 'multi_select_filter_placeholder' => 'Find..',
'intro_next_label' => 'Next', 'intro_next_label' => 'Next',
'intro_prev_label' => 'Previous', 'intro_prev_label' => 'Previous',
'intro_skip_label' => 'Skip', 'intro_skip_label' => 'Skip',
'intro_done_label' => 'Done', 'intro_done_label' => 'Done',
'between_dates_breadcrumb' => 'Between :start and :end', 'between_dates_breadcrumb' => 'Between :start and :end',
'all_journals_without_budget' => 'All transactions without a budget', 'all_journals_without_budget' => 'All transactions without a budget',
'journals_without_budget' => 'Transactions without a budget', 'journals_without_budget' => 'Transactions without a budget',
'all_journals_without_category' => 'All transactions without a category', 'all_journals_without_category' => 'All transactions without a category',
'journals_without_category' => 'Transactions without a category', 'journals_without_category' => 'Transactions without a category',
'all_journals_for_account' => 'All transactions for account :name', 'all_journals_for_account' => 'All transactions for account :name',
'chart_all_journals_for_account' => 'Chart of 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' => 'All transactions for account :name between :start and :end',
'journals_in_period_for_account_js' => 'All transactions for account {title} between {start} and {end}', 'journals_in_period_for_account_js' => 'All transactions for account {title} between {start} and {end}',
'transferred' => 'Transferred', 'transferred' => 'Transferred',
'all_withdrawal' => 'All expenses', 'all_withdrawal' => 'All expenses',
'all_transactions' => 'All transactions', 'all_transactions' => 'All transactions',
'title_withdrawal_between' => 'All expenses between :start and :end', 'title_withdrawal_between' => 'All expenses between :start and :end',
'all_deposit' => 'All revenue', 'all_deposit' => 'All revenue',
'title_deposit_between' => 'All revenue between :start and :end', 'title_deposit_between' => 'All revenue between :start and :end',
'all_transfers' => 'All transfers', 'all_transfers' => 'All transfers',
'title_transfers_between' => 'All transfers between :start and :end', 'title_transfers_between' => 'All transfers between :start and :end',
'all_transfer' => 'All transfers', 'all_transfer' => 'All transfers',
'all_journals_for_tag' => 'All transactions for tag ":tag"', 'all_journals_for_tag' => 'All transactions for tag ":tag"',
'title_transfer_between' => 'All transfers between :start and :end', 'title_transfer_between' => 'All transfers between :start and :end',
'all_journals_for_category' => 'All transactions for category :name', 'all_journals_for_category' => 'All transactions for category :name',
'all_journals_for_budget' => 'All transactions for budget :name', 'all_journals_for_budget' => 'All transactions for budget :name',
'chart_all_journals_for_budget' => 'Chart of 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_category' => 'All transactions for category :name between :start and :end',
'journals_in_period_for_tag' => 'All transactions for tag :tag 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.', '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:', '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.', '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', 'transaction_data' => 'Transaction data',
'invalid_server_configuration' => 'Invalid server configuration', '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 <a href="https://docs.firefly-iii.org/firefly-iii/advanced-installation/locales/">instructions how to do this</a>.', 'invalid_locale_settings' => 'Firefly III is unable to format monetary amounts because your server is missing the required packages. There are <a href="https://docs.firefly-iii.org/firefly-iii/advanced-installation/locales/">instructions how to do this</a>.',
'quickswitch' => 'Quickswitch', 'quickswitch' => 'Quickswitch',
'sign_in_to_start' => 'Sign in to start your session', 'sign_in_to_start' => 'Sign in to start your session',
'sign_in' => 'Sign in', 'sign_in' => 'Sign in',
'register_new_account' => 'Register a new account', 'register_new_account' => 'Register a new account',
'forgot_my_password' => 'I forgot my password', 'forgot_my_password' => 'I forgot my password',
'problems_with_input' => 'There were some problems with your input.', 'problems_with_input' => 'There were some problems with your input.',
'reset_password' => 'Reset your password', 'reset_password' => 'Reset your password',
'button_reset_password' => 'Reset password', 'button_reset_password' => 'Reset password',
'reset_button' => 'Reset', 'reset_button' => 'Reset',
'want_to_login' => 'I want to login', 'want_to_login' => 'I want to login',
'login_page_title' => 'Login to Firefly III', 'login_page_title' => 'Login to Firefly III',
'register_page_title' => 'Register at Firefly III', 'register_page_title' => 'Register at Firefly III',
'forgot_pw_page_title' => 'Forgot your password for Firefly III', 'forgot_pw_page_title' => 'Forgot your password for Firefly III',
'reset_pw_page_title' => 'Reset 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.', 'cannot_reset_demo_user' => 'You cannot reset the password of the demo user.',
'no_att_demo_user' => 'The demo user can\'t upload attachments.', 'no_att_demo_user' => 'The demo user can\'t upload attachments.',
'button_register' => 'Register', 'button_register' => 'Register',
'authorization' => 'Authorization', 'authorization' => 'Authorization',
'active_bills_only' => 'active bills only', 'active_bills_only' => 'active bills only',
'active_bills_only_total' => 'all active bills', 'active_bills_only_total' => 'all active bills',
'active_exp_bills_only' => 'active and expected bills only', 'active_exp_bills_only' => 'active and expected bills only',
'active_exp_bills_only_total' => 'all active expected bills only', 'active_exp_bills_only_total' => 'all active expected bills only',
'per_period_sum_1D' => 'Expected daily costs', 'per_period_sum_1D' => 'Expected daily costs',
'per_period_sum_1W' => 'Expected weekly costs', 'per_period_sum_1W' => 'Expected weekly costs',
'per_period_sum_1M' => 'Expected monthly costs', 'per_period_sum_1M' => 'Expected monthly costs',
'per_period_sum_3M' => 'Expected quarterly costs', 'per_period_sum_3M' => 'Expected quarterly costs',
'per_period_sum_6M' => 'Expected half-yearly costs', 'per_period_sum_6M' => 'Expected half-yearly costs',
'per_period_sum_1Y' => 'Expected yearly costs', 'per_period_sum_1Y' => 'Expected yearly costs',
'average_per_bill' => 'average per bill', 'average_per_bill' => 'average per bill',
'expected_total' => 'expected total', 'expected_total' => 'expected total',
'reconciliation_account_name' => ':name reconciliation (:currency)', 'reconciliation_account_name' => ':name reconciliation (:currency)',
'saved' => 'Saved', 'saved' => 'Saved',
'advanced_options' => 'Advanced options', '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!', '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', 'here_be_dragons' => 'Hic sunt dracones',
// Webhooks // Webhooks
'webhooks' => 'Webhooks', 'webhooks' => 'Webhooks',
// API access // API access
'authorization_request' => 'Firefly III v:version Authorization Request', 'authorization_request' => 'Firefly III v:version Authorization Request',
'authorization_request_intro' => 'Application "<strong>:client</strong>" is requesting permission to access your financial administration. Would you like to authorize <strong>:client</strong> to access these records?', 'authorization_request_intro' => 'Application "<strong>:client</strong>" is requesting permission to access your financial administration. Would you like to authorize <strong>:client</strong> to access these records?',
'authorization_request_site' => 'You will be redirected to <code>:url</code> which will then be able to access your Firefly III data.', 'authorization_request_site' => 'You will be redirected to <code>:url</code> 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.', 'authorization_request_invalid' => 'This access request is invalid. Please never follow this link again.',
'scopes_will_be_able' => 'This application will be able to:', 'scopes_will_be_able' => 'This application will be able to:',
'button_authorize' => 'Authorize', 'button_authorize' => 'Authorize',
'none_in_select_list' => '(none)', 'none_in_select_list' => '(none)',
'no_piggy_bank' => '(no piggy bank)', 'no_piggy_bank' => '(no piggy bank)',
'name_in_currency' => ':name in :currency', 'name_in_currency' => ':name in :currency',
'paid_in_currency' => 'Paid in :currency', 'paid_in_currency' => 'Paid in :currency',
'unpaid_in_currency' => 'Unpaid in :currency', 'unpaid_in_currency' => 'Unpaid in :currency',
'is_alpha_warning' => 'You are running an ALPHA version. Be wary of bugs and issues.', '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.', 'is_beta_warning' => 'You are running an BETA version. Be wary of bugs and issues.',
'all_destination_accounts' => 'Destination accounts', 'all_destination_accounts' => 'Destination accounts',
'all_source_accounts' => 'Source accounts', 'all_source_accounts' => 'Source accounts',
'back_to_index' => 'Back to the index', 'back_to_index' => 'Back to the index',
'cant_logout_guard' => 'Firefly III can\'t log you out.', 'cant_logout_guard' => 'Firefly III can\'t log you out.',
'external_url' => 'External URL', 'external_url' => 'External URL',
'internal_reference' => 'Internal reference', 'internal_reference' => 'Internal reference',
// check for updates: // check for updates:
'update_check_title' => 'Check for updates', 'update_check_title' => 'Check for updates',
'admin_update_check_title' => 'Automatically check for update', '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.', '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 <a href=":link">administration</a> to indicate if you would like this feature to be enabled.', 'check_for_updates_permission' => 'Firefly III can check for updates, but it needs your permission to do so. Please go to the <a href=":link">administration</a> to indicate if you would like this feature to be enabled.',
'updates_ask_me_later' => 'Ask me later', 'updates_ask_me_later' => 'Ask me later',
'updates_do_not_check' => 'Do not check for updates', 'updates_do_not_check' => 'Do not check for updates',
'updates_enable_check' => 'Enable the check for updates', 'updates_enable_check' => 'Enable the check for updates',
'admin_update_check_now_title' => 'Check for updates now', '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.', '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!', '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_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_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_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_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_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', 'update_check_error' => 'An error occurred while checking for updates: :error',
'unknown_error' => 'Unknown error. Sorry about that.', '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.', '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!', '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_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.', '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_stable' => 'Stable. Everything should work as expected.',
'update_channel_beta' => 'Beta. New features but things may be broken.', 'update_channel_beta' => 'Beta. New features but things may be broken.',
'update_channel_alpha' => 'Alpha. We throw stuff in, and use whatever sticks.', 'update_channel_alpha' => 'Alpha. We throw stuff in, and use whatever sticks.',
// search // search
'search' => 'Search', 'search' => 'Search',
'search_query' => 'Query', 'search_query' => 'Query',
'search_found_transactions' => 'Firefly III found :count transaction in :time seconds.|Firefly III found :count transactions in :time seconds.', '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_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: <span class="text-info">:query</span>', 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: <span class="text-info">:query</span>',
'invalid_operators_list' => 'These search parameters are not valid and have been ignored.', '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"', // old
'search_modifier_date_before' => 'Transaction date is before or on ":value"',
'search_modifier_date_after' => 'Transaction date is after or on ":value"', 'search_modifier_date_is' => 'Transaction date is ":value"',
'search_modifier_created_on' => 'Transaction was created on ":value"', 'search_modifier_id' => 'Transaction ID is ":value"',
'search_modifier_updated_on' => 'Transaction was last updated on ":value"', 'search_modifier_date_before' => 'Transaction date is before or on ":value"',
'search_modifier_external_id' => 'External ID is ":value"', 'search_modifier_date_after' => 'Transaction date is after or on ":value"',
'search_modifier_no_external_url' => 'The transaction has no external URL', 'search_modifier_external_id_is' => 'External ID is ":value"',
'search_modifier_any_external_url' => 'The transaction must have a (any) external URL', 'search_modifier_no_external_url' => 'The transaction has no external URL',
'search_modifier_internal_reference' => 'Internal reference is ":value"', 'search_modifier_any_external_url' => 'The transaction must have a (any) external URL',
'search_modifier_description_starts' => 'Description is ":value"', 'search_modifier_internal_reference_is' => 'Internal reference is ":value"',
'search_modifier_description_ends' => 'Description ends with ":value"', 'search_modifier_description_starts' => 'Description is ":value"',
'search_modifier_description_contains' => 'Description contains ":value"', 'search_modifier_description_ends' => 'Description ends with ":value"',
'search_modifier_description_is' => 'Description is exactly ":value"', 'search_modifier_description_contains' => 'Description contains ":value"',
'search_modifier_currency_is' => 'Transaction (foreign) currency is ":value"', 'search_modifier_description_is' => 'Description is exactly ":value"',
'search_modifier_foreign_currency_is' => 'Transaction foreign currency is ":value"', 'search_modifier_currency_is' => 'Transaction (foreign) currency is ":value"',
'search_modifier_has_attachments' => 'The transaction must have an attachment', 'search_modifier_foreign_currency_is' => 'Transaction foreign currency is ":value"',
'search_modifier_has_no_category' => 'The transaction must have no category', 'search_modifier_has_attachments' => 'The transaction must have an attachment',
'search_modifier_has_any_category' => 'The transaction must have a (any) category', 'search_modifier_has_no_category' => 'The transaction must have no category',
'search_modifier_has_no_budget' => 'The transaction must have no budget', 'search_modifier_has_any_category' => 'The transaction must have a (any) category',
'search_modifier_has_any_budget' => 'The transaction must have a (any) budget', 'search_modifier_has_no_budget' => 'The transaction must have no budget',
'search_modifier_has_no_bill' => 'The transaction must have no bill', 'search_modifier_has_any_budget' => 'The transaction must have a (any) budget',
'search_modifier_has_any_bill' => 'The transaction must have a (any) bill', 'search_modifier_has_no_bill' => 'The transaction must have no bill',
'search_modifier_has_no_tag' => 'The transaction must have no tags', 'search_modifier_has_any_bill' => 'The transaction must have a (any) bill',
'search_modifier_has_any_tag' => 'The transaction must have a (any) tag', 'search_modifier_has_no_tag' => 'The transaction must have no tags',
'search_modifier_notes_contain' => 'The transaction notes contain ":value"', 'search_modifier_has_any_tag' => 'The transaction must have a (any) tag',
'search_modifier_notes_start' => 'The transaction notes start with ":value"', 'search_modifier_notes_contains' => 'The transaction notes contain ":value"',
'search_modifier_notes_end' => 'The transaction notes end with ":value"', 'search_modifier_notes_starts' => 'The transaction notes start with ":value"',
'search_modifier_notes_are' => 'The transaction notes are exactly ":value"', 'search_modifier_notes_ends' => 'The transaction notes end with ":value"',
'search_modifier_no_notes' => 'The transaction has no notes', 'search_modifier_notes_is' => 'The transaction notes are exactly ":value"',
'search_modifier_any_notes' => 'The transaction must have notes', 'search_modifier_no_notes' => 'The transaction has no notes',
'search_modifier_amount_exactly' => 'Amount is exactly :value', 'search_modifier_any_notes' => 'The transaction must have notes',
'search_modifier_amount_less' => 'Amount is less than or equal to :value', 'search_modifier_amount_is' => 'Amount is exactly :value',
'search_modifier_amount_more' => 'Amount is more than or equal to :value', 'search_modifier_amount_less' => 'Amount is less than or equal to :value',
'search_modifier_source_account_is' => 'Source account name is exactly ":value"', 'search_modifier_amount_more' => 'Amount is more than or equal to :value',
'search_modifier_source_account_contains' => 'Source account name contains ":value"', 'search_modifier_source_account_is' => 'Source account name is exactly ":value"',
'search_modifier_source_account_starts' => 'Source account name starts with ":value"', 'search_modifier_source_account_contains' => 'Source account name contains ":value"',
'search_modifier_source_account_ends' => 'Source account name ends with ":value"', 'search_modifier_source_account_starts' => 'Source account name starts with ":value"',
'search_modifier_source_account_id' => 'Source account ID is :value', 'search_modifier_source_account_ends' => 'Source account name ends with ":value"',
'search_modifier_source_account_nr_is' => 'Source account number (IBAN) is ":value"', 'search_modifier_source_account_id' => 'Source account ID is :value',
'search_modifier_source_account_nr_contains' => 'Source account number (IBAN) contains ":value"', 'search_modifier_source_account_nr_is' => 'Source account number (IBAN) is ":value"',
'search_modifier_source_account_nr_starts' => 'Source account number (IBAN) starts with ":value"', 'search_modifier_source_account_nr_contains' => 'Source account number (IBAN) contains ":value"',
'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends with ":value"', 'search_modifier_source_account_nr_starts' => 'Source account number (IBAN) starts with ":value"',
'search_modifier_destination_account_is' => 'Destination account name is exactly ":value"', 'search_modifier_source_account_nr_ends' => 'Source account number (IBAN) ends with ":value"',
'search_modifier_destination_account_contains' => 'Destination account name contains ":value"', 'search_modifier_destination_account_is' => 'Destination account name is exactly ":value"',
'search_modifier_destination_account_starts' => 'Destination account name starts with ":value"', 'search_modifier_destination_account_contains' => 'Destination account name contains ":value"',
'search_modifier_destination_account_ends' => 'Destination account name ends with ":value"', 'search_modifier_destination_account_starts' => 'Destination account name starts with ":value"',
'search_modifier_destination_account_id' => 'Destination account ID is :value', 'search_modifier_destination_account_ends' => 'Destination account name ends with ":value"',
'search_modifier_destination_is_cash' => 'Destination account is (cash) account', 'search_modifier_destination_account_id' => 'Destination account ID is :value',
'search_modifier_source_is_cash' => 'Source account is (cash) account', 'search_modifier_destination_is_cash' => 'Destination account is (cash) account',
'search_modifier_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"', 'search_modifier_source_is_cash' => 'Source account is (cash) account',
'search_modifier_destination_account_nr_contains' => 'Destination account number (IBAN) contains ":value"', 'search_modifier_destination_account_nr_is' => 'Destination account number (IBAN) is ":value"',
'search_modifier_destination_account_nr_starts' => 'Destination account number (IBAN) starts with ":value"', 'search_modifier_destination_account_nr_contains' => 'Destination account number (IBAN) contains ":value"',
'search_modifier_destination_account_nr_ends' => 'Destination account number (IBAN) ends with ":value"', 'search_modifier_destination_account_nr_starts' => 'Destination account number (IBAN) starts with ":value"',
'search_modifier_account_id' => 'Source or destination account ID\'s is/are: :value', 'search_modifier_destination_account_nr_ends' => 'Destination account number (IBAN) ends with ":value"',
'search_modifier_category_is' => 'Category is ":value"', 'search_modifier_account_id' => 'Source or destination account ID\'s is/are: :value',
'search_modifier_budget_is' => 'Budget is ":value"', 'search_modifier_category_is' => 'Category is ":value"',
'search_modifier_bill_is' => 'Bill is ":value"', 'search_modifier_budget_is' => 'Budget is ":value"',
'search_modifier_transaction_type' => 'Transaction type is ":value"', 'search_modifier_bill_is' => 'Bill is ":value"',
'search_modifier_tag_is' => 'Tag is ":value"', 'search_modifier_transaction_type' => 'Transaction type is ":value"',
'search_modifier_date_is_year' => 'Transaction is in year ":value"', 'search_modifier_tag_is' => 'Tag is ":value"',
'search_modifier_date_is_month' => 'Transaction is in month ":value"', 'search_modifier_date_on_year' => 'Transaction is in year ":value"',
'search_modifier_date_is_day' => 'Transaction is on day of month ":value"', 'search_modifier_date_on_month' => 'Transaction is in month ":value"',
'search_modifier_date_before_year' => 'Transaction is before or in year ":value"', 'search_modifier_date_on_day' => 'Transaction is on day of month ":value"',
'search_modifier_date_before_month' => 'Transaction is before or in month ":value"', 'search_modifier_date_before_year' => 'Transaction is before or in year ":value"',
'search_modifier_date_before_day' => 'Transaction is before or on day of month ":value"', 'search_modifier_date_before_month' => 'Transaction is before or in month ":value"',
'search_modifier_date_after_year' => 'Transaction is in or after year ":value"', 'search_modifier_date_before_day' => 'Transaction is before or on day of month ":value"',
'search_modifier_date_after_month' => 'Transaction is in or after month ":value"', 'search_modifier_date_after_year' => 'Transaction is in or after year ":value"',
'search_modifier_date_after_day' => 'Transaction is after or on day of month ":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', 'update_rule_from_query' => 'Update rule ":rule" from search query',
'create_rule_from_query' => 'Create new 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.', '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.',