1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
declare(strict_types=1); |
4
|
|
|
|
5
|
|
|
/** |
6
|
|
|
* balloon |
7
|
|
|
* |
8
|
|
|
* @copyright Copryright (c) 2012-2018 gyselroth GmbH (https://gyselroth.com) |
9
|
|
|
* @license GPL-3.0 https://opensource.org/licenses/GPL-3.0 |
10
|
|
|
*/ |
11
|
|
|
|
12
|
|
|
namespace Balloon\App\Elasticsearch; |
13
|
|
|
|
14
|
|
|
use Balloon\Filesystem; |
15
|
|
|
use Balloon\Filesystem\Node\NodeInterface; |
16
|
|
|
use Balloon\Server; |
17
|
|
|
use Balloon\Server\User; |
18
|
|
|
use Elasticsearch\Client; |
19
|
|
|
use Elasticsearch\ClientBuilder; |
20
|
|
|
use Generator; |
21
|
|
|
use InvalidArgumentException; |
22
|
|
|
use Micro\Http\Router; |
23
|
|
|
use Psr\Log\LoggerInterface; |
24
|
|
|
|
25
|
|
|
class Elasticsearch |
26
|
|
|
{ |
27
|
|
|
/** |
28
|
|
|
* ES server. |
29
|
|
|
* |
30
|
|
|
* @var array |
31
|
|
|
*/ |
32
|
|
|
protected $es_server = ['http://localhost:9200']; |
33
|
|
|
|
34
|
|
|
/** |
35
|
|
|
* ES index. |
36
|
|
|
* |
37
|
|
|
* @var string |
38
|
|
|
*/ |
39
|
|
|
protected $es_index = 'balloon'; |
40
|
|
|
|
41
|
|
|
/** |
42
|
|
|
* ES client. |
43
|
|
|
* |
44
|
|
|
* @var Elasticsearch |
45
|
|
|
*/ |
46
|
|
|
protected $client; |
47
|
|
|
|
48
|
|
|
/** |
49
|
|
|
* User. |
50
|
|
|
* |
51
|
|
|
* @var User |
52
|
|
|
*/ |
53
|
|
|
protected $user; |
54
|
|
|
|
55
|
|
|
/** |
56
|
|
|
* Filesystem. |
57
|
|
|
* |
58
|
|
|
* @var Filesystem |
59
|
|
|
*/ |
60
|
|
|
protected $fs; |
61
|
|
|
|
62
|
|
|
/** |
63
|
|
|
* Logger. |
64
|
|
|
* |
65
|
|
|
* @var LoggerInterface |
66
|
|
|
*/ |
67
|
|
|
protected $logger; |
68
|
|
|
|
69
|
|
|
/* |
70
|
|
|
* Constructor |
71
|
|
|
* |
72
|
|
|
* @param Router |
73
|
|
|
*/ |
74
|
|
|
public function __construct(Server $server, LoggerInterface $logger, ?Iterable $config = null) |
75
|
|
|
{ |
76
|
|
|
$this->setOptions($config); |
77
|
|
|
$this->logger = $logger; |
78
|
|
|
$this->user = $server->getIdentity(); |
79
|
|
|
$this->fs = $server->getFilesystem(); |
80
|
|
|
} |
81
|
|
|
|
82
|
|
|
/** |
83
|
|
|
* Set options. |
84
|
|
|
* |
85
|
|
|
* @param iterable $config |
86
|
|
|
* |
87
|
|
|
* @return Elasticsearch |
88
|
|
|
*/ |
89
|
|
|
public function setOptions(?Iterable $config = null): self |
90
|
|
|
{ |
91
|
|
|
if (null === $config) { |
92
|
|
|
return $this; |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
foreach ($config as $option => $value) { |
96
|
|
|
switch ($option) { |
97
|
|
|
case 'server': |
|
|
|
|
98
|
|
|
$this->es_server = (array) $value; |
99
|
|
|
|
100
|
|
|
break; |
101
|
|
|
case 'index': |
102
|
|
|
$this->es_index = (string) $value; |
103
|
|
|
|
104
|
|
|
break; |
105
|
|
|
default: |
106
|
|
|
throw new InvalidArgumentException('invalid option '.$option.' given'); |
107
|
|
|
} |
108
|
|
|
} |
109
|
|
|
|
110
|
|
|
return $this; |
111
|
|
|
} |
112
|
|
|
|
113
|
|
|
/** |
114
|
|
|
* Search. |
115
|
|
|
* |
116
|
|
|
* @param array $query |
117
|
|
|
* @param int $skip |
118
|
|
|
* @param int $limit |
119
|
|
|
* @param int $total |
120
|
|
|
* |
121
|
|
|
* @return Generator |
122
|
|
|
*/ |
123
|
|
|
public function search(array $query, int $deleted = NodeInterface::DELETED_INCLUDE, ?int $skip = null, ?int $limit = null, ?int &$total = null): Generator |
124
|
|
|
{ |
125
|
|
|
$result = $this->executeQuery($query, $skip, $limit); |
126
|
|
|
|
127
|
|
|
if (isset($result['error'])) { |
128
|
|
|
throw new Exception\InvalidQuery('failed search index, query failed'); |
129
|
|
|
} |
130
|
|
|
|
131
|
|
|
$this->logger->debug('elasticsearch query executed with ['.$result['hits']['total'].'] hits', [ |
132
|
|
|
'category' => get_class($this), |
133
|
|
|
'params' => [ |
134
|
|
|
'took' => $result['took'], |
135
|
|
|
'timed_out' => $result['timed_out'], |
136
|
|
|
'_shards' => $result['_shards'], |
137
|
|
|
'max_score' => $result['hits']['max_score'], |
138
|
|
|
'hits' => $result['hits']['total'], |
139
|
|
|
], |
140
|
|
|
]); |
141
|
|
|
|
142
|
|
|
$total = $result['hits']['total']; |
143
|
|
|
|
144
|
|
|
$nodes = []; |
145
|
|
|
foreach ($result['hits']['hits'] as $node) { |
146
|
|
|
if ('storage' === $node['_type']) { |
147
|
|
|
$nodes[] = $node['_id']; |
148
|
|
|
} elseif ('fs' === $node['_type']) { |
149
|
|
|
if (isset($node['_source']['metadata']['ref'])) { |
150
|
|
|
foreach ($node['_source']['metadata']['ref'] as $blob) { |
151
|
|
|
$nodes[] = $blob['id']; |
152
|
|
|
} |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
if (isset($node['_source']['metadata']['share_ref'])) { |
156
|
|
|
foreach ($node['_source']['metadata']['share_ref'] as $blob) { |
157
|
|
|
$nodes[] = $blob['id']; |
158
|
|
|
} |
159
|
|
|
} |
160
|
|
|
} |
161
|
|
|
} |
162
|
|
|
|
163
|
|
|
return $this->fs->findNodesById($nodes, null, $deleted); |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* Get index name. |
168
|
|
|
* |
169
|
|
|
* @return string |
170
|
|
|
*/ |
171
|
|
|
public function getIndex(): string |
172
|
|
|
{ |
173
|
|
|
return $this->es_index; |
174
|
|
|
} |
175
|
|
|
|
176
|
|
|
/** |
177
|
|
|
* Get es client. |
178
|
|
|
* |
179
|
|
|
* @return Elasticsearch |
180
|
|
|
*/ |
181
|
|
|
public function getEsClient(): Client |
182
|
|
|
{ |
183
|
|
|
if ($this->client instanceof Client) { |
184
|
|
|
return $this->client; |
185
|
|
|
} |
186
|
|
|
|
187
|
|
|
return $this->client = ClientBuilder::create() |
188
|
|
|
->setHosts($this->es_server) |
189
|
|
|
->build(); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Search. |
194
|
|
|
* |
195
|
|
|
* @param array $query |
196
|
|
|
* @param int $skip |
197
|
|
|
* @param int $limit |
198
|
|
|
* |
199
|
|
|
* @return array |
200
|
|
|
*/ |
201
|
|
|
protected function executeQuery(array $query, ?int $skip = null, ?int $limit = null): array |
202
|
|
|
{ |
203
|
|
|
$shares = $this->user->getShares(true); |
204
|
|
|
$bool = $query['body']['query']; |
205
|
|
|
|
206
|
|
|
$filter1 = []; |
207
|
|
|
$filter1['bool']['should'][]['term']['owner'] = (string) $this->user->getId(); |
208
|
|
|
$filter1['bool']['should'][]['term']['metadata.ref.owner'] = (string) $this->user->getId(); |
209
|
|
|
|
210
|
|
|
$share_filter = [ |
211
|
|
|
'bool' => [ |
212
|
|
|
'should' => [], |
213
|
|
|
], |
214
|
|
|
]; |
215
|
|
|
|
216
|
|
|
foreach ($shares as $share) { |
217
|
|
|
$share_filter['bool']['should'][]['term']['metadata.share_ref.share'] = $share; |
218
|
|
|
$share_filter['bool']['should'][]['term']['reference'] = $share; |
219
|
|
|
$share_filter['bool']['should'][]['term']['shared'] = $share; |
220
|
|
|
$share_filter['bool']['minimum_should_match'] = 1; |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
if (count($share_filter['bool']['should']) >= 1) { |
224
|
|
|
$rights_filter = []; |
225
|
|
|
$rights_filter['bool']['should'][] = $share_filter; |
226
|
|
|
$rights_filter['bool']['should'][] = $filter1; |
227
|
|
|
$rights_filter['bool']['minimum_should_match'] = 1; |
228
|
|
|
} else { |
229
|
|
|
$rights_filter = $filter1; |
230
|
|
|
} |
231
|
|
|
|
232
|
|
|
$query['body']['query']['bool'] = [ |
233
|
|
|
'must' => [ |
234
|
|
|
$bool, |
235
|
|
|
$rights_filter, |
236
|
|
|
], |
237
|
|
|
]; |
238
|
|
|
|
239
|
|
|
$query['_source'] = ['metadata.*', '_id', 'owner']; |
240
|
|
|
$query['index'] = $this->es_index; |
241
|
|
|
$query['from'] = $skip; |
242
|
|
|
$query['size'] = $limit; |
243
|
|
|
|
244
|
|
|
$this->logger->debug('prepared elasticsearch query', [ |
245
|
|
|
'category' => get_class($this), |
246
|
|
|
'params' => $query, |
247
|
|
|
]); |
248
|
|
|
|
249
|
|
|
$result = $this->getEsClient()->search($query); |
250
|
|
|
|
251
|
|
|
if (null === $result) { |
252
|
|
|
$this->logger->error('failed search elastic, query returned NULL', [ |
253
|
|
|
'category' => get_class($this), |
254
|
|
|
]); |
255
|
|
|
|
256
|
|
|
throw new Exception\InvalidQuery('general search error occured'); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
return $result; |
260
|
|
|
} |
261
|
|
|
} |
262
|
|
|
|
As per the PSR-2 coding standard, case statements should not be wrapped in curly braces. There is no need for braces, since each case is terminated by the next
break
.There is also the option to use a semicolon instead of a colon, this is discouraged because many programmers do not even know it works and the colon is universal between programming languages.
To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.