Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 10
CRAP
0.00% covered (danger)
0.00%
0 / 1
CospendSearchProvider
0.00% covered (danger)
0.00%
0 / 84
0.00% covered (danger)
0.00%
0 / 10
930
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getId
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getOrder
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 search
0.00% covered (danger)
0.00%
0 / 37
0.00% covered (danger)
0.00%
0 / 1
56
 getMainText
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
132
 getSubline
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getDeepLinkToCospendApp
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 getThumbnailUrl
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
12
 truncate
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
1<?php
2
3declare(strict_types=1);
4
5/**
6 * @copyright Copyright (c) 2020, Julien Veyssier
7 *
8 * @author Julien Veyssier <eneiluj@posteo.net>
9 *
10 * @license AGPL-3.0
11 *
12 * This code is free software: you can redistribute it and/or modify
13 * it under the terms of the GNU Affero General Public License, version 3,
14 * as published by the Free Software Foundation.
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Affero General Public License for more details.
20 *
21 * You should have received a copy of the GNU Affero General Public License, version 3,
22 * along with this program. If not, see <http://www.gnu.org/licenses/>
23 *
24 */
25namespace OCA\Cospend\Search;
26
27use OCA\Cospend\Service\ProjectService;
28use OCA\Cospend\AppInfo\Application;
29use OCP\App\IAppManager;
30use OCP\IL10N;
31use OCP\IConfig;
32use OCP\IURLGenerator;
33use OCP\IUser;
34use OCP\Search\IProvider;
35use OCP\Search\ISearchQuery;
36use OCP\Search\SearchResult;
37
38use OCP\IDateTimeFormatter;
39use DateTime;
40
41class CospendSearchProvider implements IProvider {
42
43    /** @var IAppManager */
44    private $appManager;
45
46    /** @var IL10N */
47    private $l10n;
48
49    /** @var IURLGenerator */
50    private $urlGenerator;
51    /**
52     * @var IConfig
53     */
54    private $config;
55    /**
56     * @var IDateTimeFormatter
57     */
58    private $dateFormatter;
59    /**
60     * @var ProjectService
61     */
62    private $projectService;
63
64    /**
65     * CospendSearchProvider constructor.
66     *
67     * @param IAppManager $appManager
68     * @param IL10N $l10n
69     * @param IConfig $config
70     * @param IURLGenerator $urlGenerator
71     * @param IDateTimeFormatter $dateFormatter
72     * @param ProjectService $projectService
73     */
74    public function __construct(IAppManager $appManager,
75                                IL10N $l10n,
76                                IConfig $config,
77                                IURLGenerator $urlGenerator,
78                                IDateTimeFormatter $dateFormatter,
79                                ProjectService $projectService) {
80        $this->appManager = $appManager;
81        $this->l10n = $l10n;
82        $this->config = $config;
83        $this->urlGenerator = $urlGenerator;
84        $this->dateFormatter = $dateFormatter;
85        $this->projectService = $projectService;
86    }
87
88    /**
89     * @inheritDoc
90     */
91    public function getId(): string {
92        return 'cospend-search';
93    }
94
95    /**
96     * @inheritDoc
97     */
98    public function getName(): string {
99        return $this->l10n->t('Cospend');
100    }
101
102    /**
103     * @inheritDoc
104     */
105    public function getOrder(string $route, array $routeParameters): int {
106        if (strpos($route, Application::APP_ID . '.') === 0) {
107            // Active app, prefer Cospend results
108            return -1;
109        }
110
111        return 20;
112    }
113
114    /**
115     * @inheritDoc
116     */
117    public function search(IUser $user, ISearchQuery $query): SearchResult {
118        if (!$this->appManager->isEnabledForUser('cospend', $user)) {
119            return SearchResult::complete($this->getName(), []);
120        }
121
122        $limit = $query->getLimit();
123        $term = $query->getTerm();
124        $offset = $query->getCursor();
125        $offset = $offset ? (int) $offset : 0;
126
127        $resultBills = [];
128
129        // get user's projects
130        $projects = $this->projectService->getProjects($user->getUID());
131        $projectsById = [];
132        foreach ($projects as $project) {
133            $projectsById[$project['id']] = $project;
134        }
135
136        // search bills for each project
137        foreach ($projects as $project) {
138            $searchResults = $this->projectService->searchBills($project['id'], $term);
139            $resultBills = array_merge($resultBills, $searchResults);
140        }
141
142        // sort by timestamp
143        usort($resultBills, function($a, $b) {
144            $ta = $a['timestamp'];
145            $tb = $b['timestamp'];
146            return ($ta > $tb) ? -1 : 1;
147        });
148
149        $resultBills = array_slice($resultBills, $offset, $limit);
150
151        // build formatted
152        $formattedResults = array_map(function (array $bill) use ($projectsById):CospendSearchResultEntry {
153            $projectId = $bill['projectId'];
154            $thumbnailUrl = $this->getThumbnailUrl($bill);
155            return new CospendSearchResultEntry(
156                $thumbnailUrl,
157                $this->getMainText($bill, $projectsById[$projectId]),
158                $this->getSubline($bill, $projectsById[$projectId]),
159                $this->getDeepLinkToCospendApp($projectId, $bill['id']),
160                $thumbnailUrl === '' ? 'icon-cospend-search-fallback' : '',
161                true
162            );
163        }, $resultBills);
164
165        return SearchResult::paginated(
166            $this->getName(),
167            $formattedResults,
168            $offset + $limit
169        );
170    }
171
172    /**
173     * @param array $bill
174     * @param array $project
175     * @return string
176     */
177    protected function getMainText(array $bill, array $project): string {
178        $currency = $bill['currencyname'] ?? '';
179        $currency = $currency ? ' ' . $currency : '';
180        $what = $this->truncate($bill['what'], 24);
181        $catPmChars = '';
182        if (isset($bill['categoryid'])
183            && !is_null($bill['categoryid'])
184            && $bill['categoryid'] !== 0
185        ) {
186            if (isset($project['categories'][$bill['categoryid']])) {
187                $catPmChars .= $project['categories'][$bill['categoryid']]['icon'] . ' ';
188            } elseif (isset(Application::HARDCODED_CATEGORIES[$bill['categoryid']])) {
189                $catPmChars .=     Application::HARDCODED_CATEGORIES[$bill['categoryid']]['icon'];
190            }
191        }
192        if (isset($bill['paymentmodeid'])
193            && !is_null($bill['paymentmodeid'])
194            && $bill['paymentmodeid'] !== 0
195        ) {
196            if (isset($project['paymentmodes'][$bill['paymentmodeid']])) {
197                $catPmChars .= $project['paymentmodes'][$bill['paymentmodeid']]['icon'] . ' ';
198            }
199        }
200        $amount = number_format($bill['amount'], 2);
201        return $what. ' ('. $amount . $currency . ') ' . $catPmChars;
202    }
203
204    /**
205     * @param array $bill
206     * @param array $project
207     * @return string
208     */
209    protected function getSubline(array $bill, array $project): string {
210        $d = new DateTime();
211        $d->setTimestamp($bill['timestamp']);
212        $fd = $this->dateFormatter->formatDate($d, 'short');
213        return '[' . $fd . '] ' . $this->l10n->t('in %1$s', [$project['name']]);
214    }
215
216    /**
217     * @param string $projectId
218     * @return string
219     */
220    protected function getDeepLinkToCospendApp(string $projectId, int $billId): string {
221        return $this->urlGenerator->getAbsoluteURL(
222            $this->urlGenerator->linkToRoute('cospend.page.indexBill', [
223                'projectId' => $projectId,
224                'billId' => $billId,
225            ])
226        );
227    }
228
229    protected function getThumbnailUrl(array $bill): string {
230        if ($bill['payer_user_id']) {
231            return $this->urlGenerator->linkToRouteAbsolute('core.avatar.getAvatar', ['userId' => $bill['payer_user_id'], 'size' => 44]);
232        } elseif ($bill['payer_name']) {
233            return $this->urlGenerator->linkToRouteAbsolute('core.GuestAvatar.getAvatar', ['guestName' => $bill['payer_name'], 'size' => 44]);
234        }
235        return '';
236    }
237
238    /**
239     * @param string $s
240     * @param int $len
241     * @return string
242     */
243    private function truncate(string $s, int $len): string {
244        return strlen($s) > $len
245                ? substr($s, 0, $len) . '…'
246                : $s;
247    }
248}