Passed
Push — master ( d550a6...996863 )
by James
24:09 queued 11:56
created

Search::setPage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 1
1
<?php
2
/**
3
 * Search.php
4
 * Copyright (c) 2017 [email protected]
5
 *
6
 * This file is part of Firefly III.
7
 *
8
 * Firefly III is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * Firefly III is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace FireflyIII\Support\Search;
24
25
use Carbon\Carbon;
26
use FireflyIII\Helpers\Collector\GroupCollectorInterface;
27
use FireflyIII\Models\AccountType;
28
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
29
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
30
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
31
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
32
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
33
use FireflyIII\User;
34
use Illuminate\Pagination\LengthAwarePaginator;
35
use Illuminate\Support\Collection;
36
use Log;
37
38
/**
39
 * Class Search.
40
 */
41
class Search implements SearchInterface
42
{
43
    /** @var AccountRepositoryInterface */
44
    private $accountRepository;
45
    /** @var BillRepositoryInterface */
46
    private $billRepository;
47
    /** @var BudgetRepositoryInterface */
48
    private $budgetRepository;
49
    /** @var CategoryRepositoryInterface */
50
    private $categoryRepository;
51
    /** @var int */
52
    private $limit = 100;
53
    /** @var Collection */
54
    private $modifiers;
55
    /** @var string */
56
    private $originalQuery = '';
57
    /** @var float */
58
    private $startTime;
59
    /** @var TagRepositoryInterface */
60
    private $tagRepository;
61
    /** @var User */
62
    private $user;
63
    /** @var array */
64
    private $validModifiers;
65
    /** @var array */
66
    private $words = [];
67
    /** @var int */
68
    private $page;
69
70
    /**
71
     * Search constructor.
72
     */
73
    public function __construct()
74
    {
75
        $this->page               = 1;
76
        $this->modifiers          = new Collection;
77
        $this->validModifiers     = (array)config('firefly.search_modifiers');
78
        $this->startTime          = microtime(true);
79
        $this->accountRepository  = app(AccountRepositoryInterface::class);
80
        $this->categoryRepository = app(CategoryRepositoryInterface::class);
81
        $this->budgetRepository   = app(BudgetRepositoryInterface::class);
82
        $this->billRepository     = app(BillRepositoryInterface::class);
83
        $this->tagRepository      = app(TagRepositoryInterface::class);
84
85
        if ('testing' === config('app.env')) {
86
            Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this)));
87
        }
88
    }
89
90
    /**
91
     * @return Collection
92
     */
93
    public function getModifiers(): Collection
94
    {
95
        return $this->modifiers;
96
    }
97
98
    /**
99
     * @return string
100
     */
101
    public function getWordsAsString(): string
102
    {
103
        $string = implode(' ', $this->words);
104
        if ('' === $string) {
105
            return \is_string($this->originalQuery) ? $this->originalQuery : '';
0 ignored issues
show
introduced by James Cole
The condition is_string($this->originalQuery) is always true.
Loading history...
106
        }
107
108
        return $string;
109
    }
110
111
    /**
112
     * @return bool
113
     */
114
    public function hasModifiers(): bool
115
    {
116
        return $this->modifiers->count() > 0;
117
    }
118
119
    /**
120
     * @param string $query
121
     */
122
    public function parseQuery(string $query): void
123
    {
124
        $filteredQuery       = $query;
125
        $this->originalQuery = $query;
126
        $pattern             = '/[a-z_]*:[0-9a-z-.]*/i';
127
        $matches             = [];
128
        preg_match_all($pattern, $query, $matches);
129
130
        foreach ($matches[0] as $match) {
131
            $this->extractModifier($match);
132
            $filteredQuery = str_replace($match, '', $filteredQuery);
133
        }
134
        $filteredQuery = trim(str_replace(['"', "'"], '', $filteredQuery));
135
        if ('' !== $filteredQuery) {
136
            $this->words = array_map('trim', explode(' ', $filteredQuery));
137
        }
138
    }
139
140
    /**
141
     * @return float
142
     */
143
    public function searchTime(): float
144
    {
145
        return microtime(true) - $this->startTime;
146
    }
147
148
    /**
149
     * @return LengthAwarePaginator
150
     */
151
    public function searchTransactions(): LengthAwarePaginator
152
    {
153
        Log::debug('Start of searchTransactions()');
154
        $pageSize = 50;
155
156
        /** @var GroupCollectorInterface $collector */
157
        $collector = app(GroupCollectorInterface::class);
158
159
        $collector->setLimit($pageSize)->setPage($this->page)->withAccountInformation();
160
        $collector->withCategoryInformation()->withBudgetInformation();
161
        $collector->setSearchWords($this->words);
162
163
        // Most modifiers can be applied to the collector directly.
164
        $collector = $this->applyModifiers($collector);
165
166
        return $collector->getPaginatedGroups();
167
168
    }
169
170
    /**
171
     * @param int $limit
172
     */
173
    public function setLimit(int $limit): void
174
    {
175
        $this->limit = $limit;
176
    }
177
178
    /**
179
     * @param User $user
180
     */
181
    public function setUser(User $user): void
182
    {
183
        $this->user = $user;
184
        $this->accountRepository->setUser($user);
185
        $this->billRepository->setUser($user);
186
        $this->categoryRepository->setUser($user);
187
        $this->budgetRepository->setUser($user);
188
    }
189
190
    /**
191
     * @param GroupCollectorInterface $collector
192
     *
193
     * @return GroupCollectorInterface
194
     *
195
     */
196
    private function applyModifiers(GroupCollectorInterface $collector): GroupCollectorInterface
197
    {
198
        /*
199
         * TODO:
200
         * 'bill'?
201
         */
202
        $totalAccounts = new Collection;
203
204
        foreach ($this->modifiers as $modifier) {
205
            switch ($modifier['type']) {
206
                default:
207
                    die(sprintf('unsupported modifier: "%s"', $modifier['type']));
0 ignored issues
show
Best Practice introduced by James Cole
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
208
                case 'from':
209
                case 'source':
210
                    // source can only be asset, liability or revenue account:
211
                    $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE];
212
                    $accounts    = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
213
                    if ($accounts->count() > 0) {
214
                        $totalAccounts = $accounts->merge($totalAccounts);
215
                    }
216
                    break;
217
                case 'to':
218
                case 'destination':
219
                    // source can only be asset, liability or expense account:
220
                    $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE];
221
                    $accounts    = $this->accountRepository->searchAccount($modifier['value'], $searchTypes);
222
                    if ($accounts->count() > 0) {
223
                        $totalAccounts = $accounts->merge($totalAccounts);
224
                    }
225
                    break;
226
                case 'category':
227
                    $result = $this->categoryRepository->searchCategory($modifier['value']);
228
                    if ($result->count() > 0) {
229
                        $collector->setCategories($result);
230
                    }
231
                    break;
232
                case 'bill':
233
                    $result = $this->billRepository->searchBill($modifier['value']);
234
                    if ($result->count() > 0) {
235
                        $collector->setBills($result);
236
                    }
237
                    break;
238
                case 'tag':
239
                    $result = $this->tagRepository->searchTag($modifier['value']);
240
                    if ($result->count() > 0) {
241
                        $collector->setTags($result);
242
                    }
243
                    break;
244
                    break;
245
                case 'budget':
246
                    $result = $this->budgetRepository->searchBudget($modifier['value']);
247
                    if ($result->count() > 0) {
248
                        $collector->setBudgets($result);
249
                    }
250
                    break;
251
                case 'amount_is':
252
                case 'amount':
253
                    $amount = app('steam')->positive((string)$modifier['value']);
254
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount));
255
                    $collector->amountIs($amount);
256
                    break;
257
                case 'amount_max':
258
                case 'amount_less':
259
                    $amount = app('steam')->positive((string)$modifier['value']);
260
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount));
261
                    $collector->amountLess($amount);
262
                    break;
263
                case 'amount_min':
264
                case 'amount_more':
265
                    $amount = app('steam')->positive((string)$modifier['value']);
266
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $amount));
267
                    $collector->amountMore($amount);
268
                    break;
269
                case 'type':
270
                    $collector->setTypes([ucfirst($modifier['value'])]);
271
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value']));
272
                    break;
273
                case 'date':
274
                case 'on':
275
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value']));
276
                    $start = new Carbon($modifier['value']);
277
                    $collector->setRange($start, $start);
278
                    break;
279
                case 'date_before':
280
                case 'before':
281
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value']));
282
                    $before = new Carbon($modifier['value']);
283
                    $collector->setBefore($before);
284
                    break;
285
                case 'date_after':
286
                case 'after':
287
                    Log::debug(sprintf('Set "%s" using collector with value "%s"', $modifier['type'], $modifier['value']));
288
                    $after = new Carbon($modifier['value']);
289
                    $collector->setAfter($after);
290
                    break;
291
            }
292
        }
293
        $collector->setAccounts($totalAccounts);
294
295
        return $collector;
296
    }
297
298
    /**
299
     * @param string $string
300
     */
301
    private function extractModifier(string $string): void
302
    {
303
        $parts = explode(':', $string);
304
        if (2 === count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) {
305
            $type  = trim((string)$parts[0]);
306
            $value = trim((string)$parts[1]);
307
            if (in_array($type, $this->validModifiers, true)) {
308
                // filter for valid type
309
                $this->modifiers->push(['type' => $type, 'value' => $value]);
310
            }
311
        }
312
    }
313
314
    /**
315
     * @param int $page
316
     */
317
    public function setPage(int $page): void
318
    {
319
        $this->page = $page;
320
    }
321
}
322