diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php
index bb602229d3..e9b63f4b17 100644
--- a/app/Http/Controllers/SearchController.php
+++ b/app/Http/Controllers/SearchController.php
@@ -14,7 +14,7 @@ declare(strict_types = 1);
namespace FireflyIII\Http\Controllers;
use FireflyIII\Support\Search\SearchInterface;
-use Input;
+use Illuminate\Http\Request;
/**
* Class SearchController
@@ -30,12 +30,6 @@ class SearchController extends Controller
{
parent::__construct();
- $this->middleware(
- function ($request, $next) {
-
- return $next($request);
- }
- );
}
/**
@@ -45,16 +39,21 @@ class SearchController extends Controller
*
* @return $this
*/
- public function index(SearchInterface $searcher)
+ public function index(Request $request, SearchInterface $searcher)
{
-
+ $minSearchLen = 1;
$subTitle = null;
$query = null;
$result = [];
$title = trans('firefly.search');
+ $limit = 20;
$mainTitleIcon = 'fa-search';
- if (!is_null(Input::get('q')) && strlen(Input::get('q')) > 0) {
- $query = trim(Input::get('q'));
+
+ // set limit for search:
+ $searcher->setLimit($limit);
+
+ if (!is_null($request->get('q')) && strlen($request->get('q')) >= $minSearchLen) {
+ $query = trim(strtolower($request->get('q')));
$words = explode(' ', $query);
$subTitle = trans('firefly.search_results_for', ['query' => $query]);
@@ -67,7 +66,7 @@ class SearchController extends Controller
}
- return view('search.index', compact('title', 'subTitle', 'mainTitleIcon', 'query', 'result'));
+ return view('search.index', compact('title', 'subTitle', 'limit', 'mainTitleIcon', 'query', 'result'));
}
}
diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php
index b1530b08ca..91122f0d5f 100644
--- a/app/Providers/FireflyServiceProvider.php
+++ b/app/Providers/FireflyServiceProvider.php
@@ -94,7 +94,6 @@ class FireflyServiceProvider extends ServiceProvider
);
$this->app->bind('FireflyIII\Repositories\Currency\CurrencyRepositoryInterface', 'FireflyIII\Repositories\Currency\CurrencyRepository');
- $this->app->bind('FireflyIII\Support\Search\SearchInterface', 'FireflyIII\Support\Search\Search');
$this->app->bind('FireflyIII\Repositories\User\UserRepositoryInterface', 'FireflyIII\Repositories\User\UserRepository');
$this->app->bind('FireflyIII\Helpers\Attachments\AttachmentHelperInterface', 'FireflyIII\Helpers\Attachments\AttachmentHelper');
$this->app->bind(
diff --git a/app/Providers/SearchServiceProvider.php b/app/Providers/SearchServiceProvider.php
new file mode 100644
index 0000000000..a7be767e5e
--- /dev/null
+++ b/app/Providers/SearchServiceProvider.php
@@ -0,0 +1,59 @@
+app->bind(
+ 'FireflyIII\Support\Search\SearchInterface',
+ function (Application $app, array $arguments) {
+ if (!isset($arguments[0]) && $app->auth->check()) {
+ return app('FireflyIII\Support\Search\Search', [auth()->user()]);
+ }
+ if (!isset($arguments[0]) && !$app->auth->check()) {
+ throw new FireflyException('There is no user present.');
+ }
+
+ return app('FireflyIII\Support\Search\Search', $arguments);
+ }
+ );
+ }
+}
diff --git a/app/Rules/TransactionMatcher.php b/app/Rules/TransactionMatcher.php
index eb845dee29..0e63c06085 100644
--- a/app/Rules/TransactionMatcher.php
+++ b/app/Rules/TransactionMatcher.php
@@ -78,7 +78,7 @@ class TransactionMatcher
do {
// Fetch a batch of transactions from the database
$collector = new JournalCollector(auth()->user());
- $collector->setAllAssetAccounts()->setLimit($pageSize * 2)->setPage($page)->setTypes($this->transactionTypes);
+ $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->setTypes($this->transactionTypes);
$set = $collector->getPaginatedJournals();
Log::debug(sprintf('Found %d journals to check. ', $set->count()));
@@ -105,7 +105,7 @@ class TransactionMatcher
Log::debug(sprintf('Page is now %d, processed is %d', $page, $processed));
// Check for conditions to finish the loop
- $reachedEndOfList = $set->count() < $pageSize;
+ $reachedEndOfList = $set->count() < 1;
$foundEnough = $result->count() >= $this->limit;
$searchedEnough = ($processed >= $this->range);
diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php
index d20e83413f..c5e1da96c7 100644
--- a/app/Support/Search/Search.php
+++ b/app/Support/Search/Search.php
@@ -14,11 +14,15 @@ declare(strict_types = 1);
namespace FireflyIII\Support\Search;
+use FireflyIII\Helpers\Collector\JournalCollector;
+use FireflyIII\Models\Account;
use FireflyIII\Models\Budget;
use FireflyIII\Models\Category;
-use FireflyIII\Models\TransactionJournal;
-use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
+use FireflyIII\Models\Tag;
+use FireflyIII\Models\Transaction;
+use FireflyIII\User;
use Illuminate\Support\Collection;
+use Log;
/**
* Class Search
@@ -27,20 +31,46 @@ use Illuminate\Support\Collection;
*/
class Search implements SearchInterface
{
+ /** @var int */
+ private $limit = 100;
+ /** @var User */
+ private $user;
+
/**
+ * AttachmentRepository constructor.
+ *
+ * @param User $user
+ */
+ public function __construct(User $user)
+ {
+ $this->user = $user;
+ }
+
+ /**
+ * The search will assume that the user does not have so many accounts
+ * that this search should be paginated.
+ *
* @param array $words
*
* @return Collection
*/
public function searchAccounts(array $words): Collection
{
- return auth()->user()->accounts()->with('accounttype')->where(
- function (EloquentBuilder $q) use ($words) {
- foreach ($words as $word) {
- $q->orWhere('name', 'LIKE', '%' . e($word) . '%');
+ $accounts = $this->user->accounts()->get();
+ /** @var Collection $result */
+ $result = $accounts->filter(
+ function (Account $account) use ($words) {
+ if ($this->strpos_arr(strtolower($account->name), $words)) {
+ return $account;
}
+
+ return false;
}
- )->get();
+ );
+
+ $result = $result->slice(0, $this->limit);
+
+ return $result;
}
/**
@@ -51,46 +81,46 @@ class Search implements SearchInterface
public function searchBudgets(array $words): Collection
{
/** @var Collection $set */
- $set = auth()->user()->budgets()->get();
- $newSet = $set->filter(
- function (Budget $b) use ($words) {
- $found = 0;
- foreach ($words as $word) {
- if (!(strpos(strtolower($b->name), strtolower($word)) === false)) {
- $found++;
- }
+ $set = auth()->user()->budgets()->get();
+ /** @var Collection $result */
+ $result = $set->filter(
+ function (Budget $budget) use ($words) {
+ if ($this->strpos_arr(strtolower($budget->name), $words)) {
+ return $budget;
}
- return $found > 0;
+ return false;
}
);
- return $newSet;
+ $result = $result->slice(0, $this->limit);
+
+ return $result;
}
/**
+ * Search assumes the user does not have that many categories. So no paginated search.
+ *
* @param array $words
*
* @return Collection
*/
public function searchCategories(array $words): Collection
{
- /** @var Collection $set */
- $set = auth()->user()->categories()->get();
- $newSet = $set->filter(
- function (Category $c) use ($words) {
- $found = 0;
- foreach ($words as $word) {
- if (!(strpos(strtolower($c->name), strtolower($word)) === false)) {
- $found++;
- }
+ $categories = $this->user->categories()->get();
+ /** @var Collection $result */
+ $result = $categories->filter(
+ function (Category $category) use ($words) {
+ if ($this->strpos_arr(strtolower($category->name), $words)) {
+ return $category;
}
- return $found > 0;
+ return false;
}
);
+ $result = $result->slice(0, $this->limit);
- return $newSet;
+ return $result;
}
/**
@@ -101,7 +131,21 @@ class Search implements SearchInterface
*/
public function searchTags(array $words): Collection
{
- return new Collection;
+ $tags = $this->user->tags()->get();
+
+ /** @var Collection $result */
+ $result = $tags->filter(
+ function (Tag $tag) use ($words) {
+ if ($this->strpos_arr(strtolower($tag->tag), $words)) {
+ return $tag;
+ }
+
+ return false;
+ }
+ );
+ $result = $result->slice(0, $this->limit);
+
+ return $result;
}
/**
@@ -111,40 +155,86 @@ class Search implements SearchInterface
*/
public function searchTransactions(array $words): Collection
{
- // decrypted transaction journals:
- $decrypted = auth()->user()->transactionJournals()->expanded()->where('transaction_journals.encrypted', 0)->where(
- function (EloquentBuilder $q) use ($words) {
- foreach ($words as $word) {
- $q->orWhere('transaction_journals.description', 'LIKE', '%' . e($word) . '%');
- }
- }
- )->get(TransactionJournal::queryFields());
+ $pageSize = 100;
+ $processed = 0;
+ $page = 1;
+ $result = new Collection();
+ do {
+ $collector = new JournalCollector($this->user);
+ $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page);
+ $set = $collector->getPaginatedJournals();
+ Log::debug(sprintf('Found %d journals to check. ', $set->count()));
- // encrypted
- $all = auth()->user()->transactionJournals()->expanded()->where('transaction_journals.encrypted', 1)->get(TransactionJournal::queryFields());
- $set = $all->filter(
- function (TransactionJournal $journal) use ($words) {
- foreach ($words as $word) {
- $haystack = strtolower($journal->description);
- $word = strtolower($word);
- if (!(strpos($haystack, $word) === false)) {
- return $journal;
+ // Filter transactions that match the given triggers.
+ $filtered = $set->filter(
+ function (Transaction $transaction) use ($words) {
+ // check descr of journal:
+ if ($this->strpos_arr(strtolower(strval($transaction->description)), $words)) {
+ return $transaction;
}
+
+ // check descr of transaction
+ if ($this->strpos_arr(strtolower(strval($transaction->transaction_description)), $words)) {
+ return $transaction;
+ }
+
+ // return false:
+ return false;
}
+ );
- return null;
+ Log::debug(sprintf('Found %d journals that match.', $filtered->count()));
+ // merge:
+ /** @var Collection $result */
+ $result = $result->merge($filtered);
+ Log::debug(sprintf('Total count is now %d', $result->count()));
+
+ // Update counters
+ $page++;
+ $processed += count($set);
+
+ Log::debug(sprintf('Page is now %d, processed is %d', $page, $processed));
+
+ // Check for conditions to finish the loop
+ $reachedEndOfList = $set->count() < 1;
+ $foundEnough = $result->count() >= $this->limit;
+
+ Log::debug(sprintf('reachedEndOfList: %s', var_export($reachedEndOfList, true)));
+ Log::debug(sprintf('foundEnough: %s', var_export($foundEnough, true)));
+
+ } while (!$reachedEndOfList && !$foundEnough);
+
+ $result = $result->slice(0, $this->limit);
+
+ return $result;
+ }
+
+ /**
+ * @param int $limit
+ */
+ public function setLimit(int $limit)
+ {
+ $this->limit = $limit;
+ }
+
+ /**
+ * @param string $haystack
+ * @param array $needle
+ *
+ * @return bool
+ */
+ private function strpos_arr(string $haystack, array $needle)
+ {
+ if (strlen($haystack) === 0) {
+ return false;
+ }
+ foreach ($needle as $what) {
+ if (($pos = strpos($haystack, $what)) !== false) {
+ return true;
}
- );
- $filtered = $set->merge($decrypted);
- $filtered = $filtered->sortBy(
- function (TransactionJournal $journal) {
- return intval($journal->date->format('U'));
- }
- );
+ }
- $filtered = $filtered->reverse();
-
- return $filtered;
+ return false;
}
}
diff --git a/config/app.php b/config/app.php
index 0611c68b7f..478fae8b8d 100755
--- a/config/app.php
+++ b/config/app.php
@@ -208,6 +208,7 @@ return [
FireflyIII\Providers\PiggyBankServiceProvider::class,
FireflyIII\Providers\RuleServiceProvider::class,
FireflyIII\Providers\RuleGroupServiceProvider::class,
+ FireflyIII\Providers\SearchServiceProvider::class,
FireflyIII\Providers\TagServiceProvider::class,
diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php
index 8519610ca7..b7643c4751 100644
--- a/resources/lang/en_US/firefly.php
+++ b/resources/lang/en_US/firefly.php
@@ -67,7 +67,19 @@ return [
'warning_much_data' => ':days days of data may take a while to load.',
'registered' => 'You have registered successfully!',
'search' => 'Search',
+ 'search_found_accounts' => 'Found :count account(s) for your query.',
+ 'search_found_categories' => 'Found :count category(ies) for your query.',
+ 'search_found_budgets' => 'Found :count budget(s) for your query.',
+ 'search_found_tags' => 'Found :count tag(s) for your query.',
+ 'search_found_transactions' => 'Found :count transaction(s) for your query.',
+ 'results_limited' => 'The results are limited to :count entries.',
+ 'tagbalancingAct' => 'Balancing act',
+ 'tagadvancePayment' => 'Advance payment',
+ 'tagnothing' => '',
+ 'Default asset account' => 'Default asset account',
'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.',
+ 'Savings account' => 'Savings account',
+ 'Credit card' => 'Credit card',
'source_accounts' => 'Source account(s)',
'destination_accounts' => 'Destination account(s)',
'user_id_is' => 'Your user id is :user',
diff --git a/resources/views/search/index.twig b/resources/views/search/index.twig
index 35bf5a3d28..88107c52d6 100644
--- a/resources/views/search/index.twig
+++ b/resources/views/search/index.twig
@@ -5,19 +5,35 @@
{% endblock %}
{% block content %}
- {% if query %}
+ {% if query == "" %}
+
+
{{ 'no_results_for_empty_search'|_ }}
+
+
+ {% endif %}
+ {% if query %}
+
+
+
+
{{ trans('firefly.results_limited', {count: limit}) }}
+
+
+
+
+
+
{% if result.transactions|length > 0 %}
-
- {% include 'list.journals-tiny' with {'transactions' : result.transactions} %}
-
-
@@ -26,19 +42,13 @@
@@ -47,13 +57,13 @@
@@ -62,19 +72,13 @@
@@ -83,19 +87,13 @@
@@ -106,8 +104,8 @@
{% endblock %}
-{% block scripts %}
-
-{% endblock %}
+ {% block scripts %}
+
+ {% endblock %}
diff --git a/resources/views/search/partials/accounts.twig b/resources/views/search/partials/accounts.twig
new file mode 100644
index 0000000000..e21b443d59
--- /dev/null
+++ b/resources/views/search/partials/accounts.twig
@@ -0,0 +1,34 @@
+
+
+
+ |
+ {{ trans('list.name') }} |
+ {{ trans('list.type') }} |
+ {{ trans('list.role') }} |
+ {{ trans('list.iban') }} |
+
+
+
+ {% for account in result.accounts %}
+
+
+
+ |
+ {{ account.name }} |
+ {{ trans('firefly.'~account.accountType.type) }} |
+
+ {% for entry in account.accountmeta %}
+ {% if entry.name == 'accountRole' %}
+ {{ trans('firefly.'~entry.data|getAccountRole) }}
+ {% endif %}
+ {% endfor %}
+ |
+ {{ account.iban }} |
+
+ {% endfor %}
+
+
+
diff --git a/resources/views/search/partials/budgets.twig b/resources/views/search/partials/budgets.twig
new file mode 100644
index 0000000000..ad14e57747
--- /dev/null
+++ b/resources/views/search/partials/budgets.twig
@@ -0,0 +1,22 @@
+
+
+
+ |
+ {{ trans('list.name') }} |
+
+
+
+ {% for budget in result.budgets %}
+
+
+
+ |
+ {{ budget.name }} |
+
+ {% endfor %}
+
+
+
diff --git a/resources/views/search/partials/categories.twig b/resources/views/search/partials/categories.twig
new file mode 100644
index 0000000000..4176de3240
--- /dev/null
+++ b/resources/views/search/partials/categories.twig
@@ -0,0 +1,22 @@
+
+
+
+ |
+ {{ trans('list.name') }} |
+
+
+
+ {% for category in result.categories %}
+
+
+
+ |
+ {{ category.name }} |
+
+ {% endfor %}
+
+
+
diff --git a/resources/views/search/partials/tags.twig b/resources/views/search/partials/tags.twig
new file mode 100644
index 0000000000..c4b3e21a2c
--- /dev/null
+++ b/resources/views/search/partials/tags.twig
@@ -0,0 +1,24 @@
+
+
+
+ |
+ {{ trans('list.name') }} |
+ {{ trans('list.type') }} |
+
+
+
+ {% for tag in result.tags %}
+
+
+
+ |
+ {{ tag.tag }} |
+ {{ ('tag'~tag.tagMode)|_ }} |
+
+ {% endfor %}
+
+
+
diff --git a/resources/views/search/partials/transactions.twig b/resources/views/search/partials/transactions.twig
new file mode 100644
index 0000000000..c20da1ff07
--- /dev/null
+++ b/resources/views/search/partials/transactions.twig
@@ -0,0 +1,81 @@
+{{ journals.render|raw }}
+
+
+
+
+
+ {{ journals.render|raw }}
+
+
+