Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.22% |
83 / 92 |
|
68.00% |
17 / 25 |
CRAP | |
0.00% |
0 / 1 |
SearchProfileService | |
90.22% |
83 / 92 |
|
68.00% |
17 / 25 |
55.63 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
hasProfile | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
supportsContext | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
loadProfileByName | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
5 | |||
loadProfile | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
4.13 | |||
getProfileName | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
8.02 | |||
registerRepository | |
60.00% |
3 / 5 |
|
0.00% |
0 / 1 |
2.26 | |||
registerArrayRepository | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
registerFileRepository | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
listExposedProfiles | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
5 | |||
registerDefaultProfile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
registerProfileOverride | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
registerConfigOverride | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
registerUriParamOverride | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
registerUserPrefOverride | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
registerContextualOverride | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
registerSearchQueryRoute | |
75.00% |
3 / 4 |
|
0.00% |
0 / 1 |
2.06 | |||
registerFTSearchQueryRoute | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
getDispatchService | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
freeze | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
checkFrozen | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
listProfileTypes | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listProfileContexts | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listProfileRepositories | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
listProfileOverrides | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Profile; |
4 | |
5 | use CirrusSearch\BuildDocument\DocumentSizeLimiter; |
6 | use CirrusSearch\Dispatch\BasicSearchQueryRoute; |
7 | use CirrusSearch\Dispatch\CirrusDefaultSearchQueryRoute; |
8 | use CirrusSearch\Dispatch\DefaultSearchQueryDispatchService; |
9 | use CirrusSearch\Dispatch\SearchQueryDispatchService; |
10 | use CirrusSearch\Dispatch\SearchQueryRoute; |
11 | use CirrusSearch\Search\SearchQuery; |
12 | use MediaWiki\Config\Config; |
13 | use MediaWiki\Request\WebRequest; |
14 | use MediaWiki\User\Options\UserOptionsLookup; |
15 | use MediaWiki\User\UserIdentity; |
16 | use RequestContext; |
17 | use Wikimedia\Assert\Assert; |
18 | |
19 | /** |
20 | * Service to manage and access search profiles. |
21 | * Search profiles are arranged by type identified by a string constant: |
22 | * - COMPLETION: profiles used for autocomplete search when running the completion suggester |
23 | * - CROSS_PROJECT_BLOCK_SCORER: used when reordering blocks of crossproject search results |
24 | * - FT_QUERY_BUILDER: used when building fulltext search queries |
25 | * - PHRASE_SUGGESTER: Controls the behavior of the phrase suggester (did you mean suggestions) |
26 | * - INDEX_LOOKUP_FALLBACK: Controls the behavior of the index lookup fallback method (did you mean suggestions) |
27 | * - RESCORE: Controls how elasticsearch rescore queries are built |
28 | * - RESCORE_FUNCTION_CHAINS: Controls the list of functions used by a rescore profile |
29 | * - SANEITIZER: Controls the saneitizer |
30 | * - SIMILARITY: Defines similarity profiles used when building the index |
31 | * |
32 | * Multiple repository per type can be declared, in general we have: |
33 | * - the cirrus_base repository holding the default profiles contained in cirrus code |
34 | * - the cirrus_config repository holding the profiles customized using $wgCirrusSearch config vars. |
35 | * |
36 | * The service is bound to a SearchConfig instance which means that the profiles may vary depending |
37 | * on the SearchConfig being used. The cirrus_base repository will always hold the same set of |
38 | * profiles but the cirrus_config may change according to SearchConfig content. |
39 | * |
40 | * The service is also responsible for determining the name of the default profile for a given context. |
41 | * The profile context is a notion introduced to allow using the same profile for multiple purposes. |
42 | * For example the rescore profiles may be used for different kind of queries (fulltext vs prefixsearch). |
43 | * While they share the same set of profiles we may prefer to use different defaults depending on the |
44 | * type of the query. The profile context allows to distinguish between these use cases. |
45 | * |
46 | * Then in order to customize the default profile the service allows to define a list of "overriders": |
47 | * - ConfigSearchProfileOverride: overrides the default profile by reading a config var |
48 | * - UriParamSearchProfileOverride: overrides the default profile by inspecting the URI params |
49 | * - UserPrefSearchProfileOverride: overrides the default profile by inspecting the user prefs |
50 | */ |
51 | class SearchProfileService { |
52 | |
53 | /** |
54 | * Profile type for ordering crossproject result blocks |
55 | */ |
56 | public const CROSS_PROJECT_BLOCK_SCORER = 'crossproject_block_scorer'; |
57 | |
58 | /** |
59 | * Profile type for similarity configuration |
60 | * Used when building the indices |
61 | */ |
62 | public const SIMILARITY = 'similarity'; |
63 | |
64 | /** |
65 | * Profile type for rescoring components |
66 | * Used at query when building elastic queries |
67 | * @see \CirrusSearch\Search\Rescore\RescoreBuilder |
68 | */ |
69 | public const RESCORE = 'rescore'; |
70 | |
71 | /** |
72 | * Profile type used to build function chains |
73 | * Used at query time by rescore builders |
74 | * @see \CirrusSearch\Search\Rescore\RescoreBuilder |
75 | */ |
76 | public const RESCORE_FUNCTION_CHAINS = 'rescore_function_chains'; |
77 | |
78 | /** |
79 | * Profile type used by the completion suggester |
80 | * @see \CirrusSearch\CompletionSuggester |
81 | */ |
82 | public const COMPLETION = 'completion'; |
83 | |
84 | /** |
85 | * Profile type used by the phrase suggester (fulltext search only) |
86 | * @see \CirrusSearch\Fallbacks\PhraseSuggestFallbackMethod |
87 | */ |
88 | public const PHRASE_SUGGESTER = 'phrase_suggester'; |
89 | |
90 | /** |
91 | * Profile type used by the index lookup fallback method method |
92 | * @see \CirrusSearch\Fallbacks\IndexLookupFallbackMethod |
93 | */ |
94 | public const INDEX_LOOKUP_FALLBACK = 'index_lookup_fallback'; |
95 | |
96 | /** |
97 | * Profile type used by saneitizer |
98 | * @see \CirrusSearch\Maintenance\SaneitizeJobs |
99 | */ |
100 | public const SANEITIZER = 'saneitizer'; |
101 | |
102 | /** |
103 | * Profile type used by the document size limiter |
104 | * @see DocumentSizeLimiter |
105 | */ |
106 | public const DOCUMENT_SIZE_LIMITER = 'document_size_limiter'; |
107 | |
108 | /** |
109 | * Profiles used for building fulltext search queries |
110 | * @see \CirrusSearch\Search\SearchContext::getFulltextQueryBuilderProfile() |
111 | */ |
112 | public const FT_QUERY_BUILDER = 'ft_query_builder'; |
113 | |
114 | /** |
115 | * Profile type used by FallbackRunner. |
116 | * @see \CirrusSearch\Fallbacks\FallbackRunner::create() |
117 | */ |
118 | public const FALLBACKS = 'fallbacks'; |
119 | |
120 | /** |
121 | * Profile context used for prefix search queries |
122 | */ |
123 | public const CONTEXT_PREFIXSEARCH = 'prefixsearch'; |
124 | |
125 | /** |
126 | * Default profile context (used by fulltext queries) |
127 | */ |
128 | public const CONTEXT_DEFAULT = 'default'; |
129 | |
130 | /** |
131 | * List of profile repositories, grouped by type and then by repository name. |
132 | * @var SearchProfileRepository[][] |
133 | */ |
134 | private $repositories = []; |
135 | |
136 | /** |
137 | * List of default profile names to use for a given type in a given context |
138 | * Key path is [type][context] |
139 | * @var string[][] |
140 | */ |
141 | private $defaultProfiles = []; |
142 | |
143 | /** |
144 | * list of overriders, $this->overriders[$type][$context] is an array of SearchProfileOverride |
145 | * Key path is [type][context] |
146 | * @var SearchProfileOverride[][][] |
147 | */ |
148 | private $overriders = []; |
149 | |
150 | /** |
151 | * @var UserIdentity |
152 | */ |
153 | private $user; |
154 | |
155 | /** |
156 | * @var WebRequest |
157 | */ |
158 | private $request; |
159 | |
160 | /** |
161 | * @var bool |
162 | */ |
163 | private $frozen; |
164 | |
165 | /** |
166 | * @var SearchQueryDispatchService|null (lazy loaded) |
167 | */ |
168 | private $dispatchService; |
169 | |
170 | /** |
171 | * @var SearchQueryRoute[][] |
172 | */ |
173 | private $routes; |
174 | |
175 | /** |
176 | * @var UserOptionsLookup |
177 | */ |
178 | private $userOptionsLookup; |
179 | |
180 | /** |
181 | * @param UserOptionsLookup $userOptionsLookup |
182 | * @param WebRequest|null $request obtained from \RequestContext::getMain()->getRequest() if null |
183 | * @param UserIdentity|null $user obtained from \RequestContext::getMain()->getUser() if null |
184 | */ |
185 | public function __construct( |
186 | UserOptionsLookup $userOptionsLookup, |
187 | WebRequest $request = null, |
188 | UserIdentity $user = null |
189 | ) { |
190 | $this->userOptionsLookup = $userOptionsLookup; |
191 | $this->request = $request ?? RequestContext::getMain()->getRequest(); |
192 | $this->user = $user ?? RequestContext::getMain()->getUser(); |
193 | $this->routes = [ |
194 | 'searchText' => [ CirrusDefaultSearchQueryRoute::searchTextDefaultRoute() ] |
195 | ]; |
196 | } |
197 | |
198 | /** |
199 | * @param string $type |
200 | * @param string $name |
201 | * @return bool |
202 | */ |
203 | public function hasProfile( $type, $name ) { |
204 | if ( isset( $this->repositories[$type] ) ) { |
205 | foreach ( $this->repositories[$type] as $repo ) { |
206 | if ( $repo->hasProfile( $name ) ) { |
207 | return true; |
208 | } |
209 | } |
210 | } |
211 | return false; |
212 | } |
213 | |
214 | /** |
215 | * @param string $type |
216 | * @param string $context |
217 | * @return bool |
218 | */ |
219 | public function supportsContext( $type, $context ) { |
220 | return isset( $this->defaultProfiles[$type][$context] ); |
221 | } |
222 | |
223 | /** |
224 | * Load a profile by its name. |
225 | * It's better to use self::loadProfile and let the service |
226 | * determine the proper profile to use in a given context. |
227 | * |
228 | * @param string $type the type of the profile (see class doc) |
229 | * @param string $name |
230 | * @param bool $failIfMissing when true will throw SearchProfileException |
231 | * @return array|null |
232 | */ |
233 | public function loadProfileByName( $type, $name, $failIfMissing = true ) { |
234 | if ( isset( $this->repositories[$type] ) ) { |
235 | $repos = $this->repositories[$type]; |
236 | foreach ( $repos as $repo ) { |
237 | $prof = $repo->getProfile( $name ); |
238 | if ( $prof !== null ) { |
239 | return $prof; |
240 | } |
241 | } |
242 | } |
243 | if ( $failIfMissing ) { |
244 | throw new SearchProfileException( "Cannot load a profile type $type: $name not found" ); |
245 | } |
246 | return null; |
247 | } |
248 | |
249 | /** |
250 | * Load a profile for the context or by its name if name is provided |
251 | * |
252 | * @param string $type |
253 | * @param string $context used to determine the name of the profile if $name is not provided |
254 | * @param string|null $name force the name of the profile to use |
255 | * @param string[] $contextParams Parameters of the context, for determining the profile |
256 | * name. Some overriders use these to decide if an override is appropriate. |
257 | * @return array |
258 | * @see self::getProfileName() |
259 | */ |
260 | public function loadProfile( $type, $context = self::CONTEXT_DEFAULT, $name = null, $contextParams = [] ) { |
261 | if ( $name === null && $context === null ) { |
262 | throw new SearchProfileException( '$name and $context cannot be both null' ); |
263 | } |
264 | if ( $name === null ) { |
265 | $name = $this->getProfileName( $type, $context, $contextParams ); |
266 | } |
267 | return $this->loadProfileByName( $type, $name ); |
268 | } |
269 | |
270 | /** |
271 | * @param string $type the type of the profile (see class doc) |
272 | * @param string $context |
273 | * @param string[] $contextParams Parameters of the context, for determining the profile |
274 | * name. Some overriders use these to decide if an override is appropriate. |
275 | * @return string |
276 | */ |
277 | public function getProfileName( $type, $context = self::CONTEXT_DEFAULT, array $contextParams = [] ) { |
278 | $minPrio = PHP_INT_MAX; |
279 | if ( !isset( $this->defaultProfiles[$type][$context] ) ) { |
280 | throw new SearchProfileException( "No default profile found for $type in context $context" ); |
281 | } |
282 | $profile = $this->defaultProfiles[$type][$context]; |
283 | if ( !$this->hasProfile( $type, $profile ) ) { |
284 | throw new SearchProfileException( "The default profile $profile does not exist in profile repositories of type $type" ); |
285 | } |
286 | |
287 | if ( !isset( $this->overriders[$type][$context] ) ) { |
288 | return $profile; |
289 | } |
290 | |
291 | foreach ( $this->overriders[$type][$context] as $overrider ) { |
292 | if ( $overrider->priority() < $minPrio ) { |
293 | $name = $overrider->getOverriddenName( $contextParams ); |
294 | if ( $name !== null && $this->hasProfile( $type, $name ) ) { |
295 | $minPrio = $overrider->priority(); |
296 | $profile = $name; |
297 | } |
298 | } |
299 | } |
300 | return $profile; |
301 | } |
302 | |
303 | /** |
304 | * Register a new profile repository |
305 | * @param SearchProfileRepository $repository |
306 | */ |
307 | public function registerRepository( SearchProfileRepository $repository ) { |
308 | $this->checkFrozen(); |
309 | if ( isset( $this->repositories[$repository->repositoryType()][$repository->repositoryName()] ) ) { |
310 | throw new SearchProfileException( "A profile repository type {$repository->repositoryType()} " . |
311 | "named {$repository->repositoryName()} is already registered." ); |
312 | } |
313 | $this->repositories[$repository->repositoryType()][$repository->repositoryName()] = $repository; |
314 | } |
315 | |
316 | /** |
317 | * Register a new repository backed by a simple array |
318 | * @param string $repoType |
319 | * @param string $repoName |
320 | * @param array $profiles |
321 | */ |
322 | public function registerArrayRepository( $repoType, $repoName, array $profiles ) { |
323 | $this->registerRepository( ArrayProfileRepository::fromArray( $repoType, $repoName, $profiles ) ); |
324 | } |
325 | |
326 | /** |
327 | * Register a new repository backed by a PHP file returning an array. |
328 | * |
329 | * <b>NOTE:</b> $phpFile is loaded with PHP's require keyword. |
330 | * |
331 | * @param string $type |
332 | * @param string $name |
333 | * @param string $phpFile |
334 | * @see FileProfileRepository |
335 | */ |
336 | public function registerFileRepository( $type, $name, $phpFile ) { |
337 | $this->registerRepository( ArrayProfileRepository::fromFile( $type, $name, $phpFile ) ); |
338 | } |
339 | |
340 | /** |
341 | * List profiles under type $type that are suited |
342 | * to be exposed to the users. |
343 | * |
344 | * This method is provided for convenience and to help |
345 | * users to discover existing profile. |
346 | * It's possible that an existing profile may not be listed here |
347 | * so this method must not be used to verify the existence of a given |
348 | * profile. Use hasProfile instead. |
349 | * |
350 | * @param string $type |
351 | * @return array |
352 | */ |
353 | public function listExposedProfiles( $type ) { |
354 | $profiles = []; |
355 | if ( isset( $this->repositories[$type] ) ) { |
356 | foreach ( $this->repositories[$type] as $repo ) { |
357 | foreach ( $repo->listExposedProfiles() as $name => $profile ) { |
358 | if ( !isset( $profiles[$name] ) ) { |
359 | $profiles[$name] = $profile; |
360 | } |
361 | } |
362 | } |
363 | } |
364 | return $profiles; |
365 | } |
366 | |
367 | /** |
368 | * Register a default profile named $profileName for $type in context $profileContext |
369 | * It must be an existing profile otherwise it will always fail when trying to determine |
370 | * the profile name. |
371 | * @param string $type |
372 | * @param string $profileContext |
373 | * @param string $profileName |
374 | */ |
375 | public function registerDefaultProfile( $type, $profileContext, $profileName ) { |
376 | if ( isset( $this->defaultProfiles[$type][$profileContext] ) ) { |
377 | throw new SearchProfileException( "A default profile already exists for $type in context $profileContext" ); |
378 | } |
379 | $this->defaultProfiles[$type][$profileContext] = $profileName; |
380 | } |
381 | |
382 | /** |
383 | * Register a new profile overrider. |
384 | * It allows to override the default profile based on the implementation of SearchProfileOverride. |
385 | * @param string $type |
386 | * @param string|string[] $profileContext one or multiple contexts |
387 | * @param SearchProfileOverride $override |
388 | */ |
389 | public function registerProfileOverride( $type, $profileContext, SearchProfileOverride $override ) { |
390 | $this->checkFrozen(); |
391 | if ( !is_array( $profileContext ) ) { |
392 | $profileContext = [ $profileContext ]; |
393 | } |
394 | foreach ( $profileContext as $context ) { |
395 | $this->overriders[$type][$context][] = $override; |
396 | } |
397 | } |
398 | |
399 | /** |
400 | * Register a new overrider using the ConfigSearchProfileOverride implementation |
401 | * @param string $type |
402 | * @param string|string[] $profileContext one or multiple contexts |
403 | * @param Config $config |
404 | * @param string $configEntry |
405 | * @see ConfigSearchProfileOverride |
406 | */ |
407 | public function registerConfigOverride( $type, $profileContext, Config $config, $configEntry ) { |
408 | $this->registerProfileOverride( $type, $profileContext, new ConfigSearchProfileOverride( $config, $configEntry ) ); |
409 | } |
410 | |
411 | /** |
412 | * @param string $type |
413 | * @param string|string[] $profileContext one or multiple contexts |
414 | * @param string $uriParam |
415 | */ |
416 | public function registerUriParamOverride( $type, $profileContext, $uriParam ) { |
417 | $this->registerProfileOverride( $type, $profileContext, new UriParamSearchProfileOverride( $this->request, $uriParam ) ); |
418 | } |
419 | |
420 | /** |
421 | * @param string $type |
422 | * @param string|string[] $profileContext one or multiple contexts |
423 | * @param string $userPref the name of the key used to store this user preference |
424 | */ |
425 | public function registerUserPrefOverride( $type, $profileContext, $userPref ) { |
426 | $this->registerProfileOverride( |
427 | $type, |
428 | $profileContext, |
429 | new UserPrefSearchProfileOverride( $this->user, $this->userOptionsLookup, $userPref ) ); |
430 | } |
431 | |
432 | /** |
433 | * @param string $type |
434 | * @param string|string[] $profileContext one or multiple contexts |
435 | * @param string $template A templated profile name |
436 | * @param string[] $params Map from string in $template to context parameter to |
437 | * replace with. All params must be available in the context parameters or |
438 | * no override will be applied. |
439 | */ |
440 | public function registerContextualOverride( $type, $profileContext, $template, array $params ) { |
441 | $this->registerProfileOverride( $type, $profileContext, new ContextualProfileOverride( $template, $params ) ); |
442 | } |
443 | |
444 | /** |
445 | * Register a new route to be used by the SearchQueryDispatchService |
446 | * |
447 | * @param SearchQueryRoute $route |
448 | * @see SearchQueryDispatchService |
449 | * @see SearchProfileService::getDispatchService() |
450 | */ |
451 | public function registerSearchQueryRoute( SearchQueryRoute $route ) { |
452 | $this->checkFrozen(); |
453 | if ( !isset( $this->routes[$route->getSearchEngineEntryPoint()] ) ) { |
454 | throw new SearchProfileException( "Unsupported search engine entry point {$route->getSearchEngineEntryPoint()}" ); |
455 | } |
456 | $this->routes[$route->getSearchEngineEntryPoint()][] = $route; |
457 | } |
458 | |
459 | /** |
460 | * Register a new static route for fulltext search queries. |
461 | * |
462 | * @param string $profileContext |
463 | * @param float $score score of the route |
464 | * @param int[] $supportedNamespaces |
465 | * @param string[] $acceptableQueryClasses |
466 | * @see SearchProfileService::getDispatchService() |
467 | * @see SearchQueryDispatchService::CIRRUS_DEFAULTS_SCORE |
468 | */ |
469 | public function registerFTSearchQueryRoute( |
470 | $profileContext, |
471 | $score, |
472 | array $supportedNamespaces, |
473 | array $acceptableQueryClasses = [] |
474 | ) { |
475 | Assert::parameter( $score > SearchQueryDispatchService::CIRRUS_DEFAULTS_SCORE, '$score', |
476 | "This route will never be selected it must " . |
477 | "be greater than " . SearchQueryDispatchService::CIRRUS_DEFAULTS_SCORE |
478 | ); |
479 | $this->registerSearchQueryRoute( new BasicSearchQueryRoute( SearchQuery::SEARCH_TEXT, |
480 | $supportedNamespaces, $acceptableQueryClasses, $profileContext, $score ) ); |
481 | } |
482 | |
483 | /** |
484 | * Return the service responsible for dispatching a SearchQuery |
485 | * to its preferred profile context. |
486 | * |
487 | * @return SearchQueryDispatchService |
488 | */ |
489 | public function getDispatchService(): SearchQueryDispatchService { |
490 | if ( $this->dispatchService === null ) { |
491 | Assert::precondition( $this->frozen, |
492 | "Must be frozen when accessing the SearchQuery dispatch service." ); |
493 | $this->dispatchService = new DefaultSearchQueryDispatchService( $this->routes ); |
494 | } |
495 | return $this->dispatchService; |
496 | } |
497 | |
498 | /** |
499 | * Freeze the service, any attempt to declare a new repository |
500 | * will fail. |
501 | */ |
502 | public function freeze() { |
503 | $this->frozen = true; |
504 | } |
505 | |
506 | private function checkFrozen() { |
507 | if ( $this->frozen ) { |
508 | throw new SearchProfileException( self::class . " is frozen, you cannot register new repositories/overriders." ); |
509 | } |
510 | } |
511 | |
512 | /** |
513 | * @return string[] |
514 | */ |
515 | public function listProfileTypes() { |
516 | return array_keys( $this->repositories ); |
517 | } |
518 | |
519 | /** |
520 | * List default profile per context |
521 | * @param string $type |
522 | * @return string[] context is the key, the default profile name |
523 | */ |
524 | public function listProfileContexts( $type ) { |
525 | return $this->defaultProfiles[$type] ?? []; |
526 | } |
527 | |
528 | /** |
529 | * List profile repositories |
530 | * @param string $type |
531 | * @return SearchProfileRepository[] |
532 | */ |
533 | public function listProfileRepositories( $type ) { |
534 | return $this->repositories[$type] ?? []; |
535 | } |
536 | |
537 | /** |
538 | * L |
539 | * @param string $type |
540 | * @param string $context |
541 | * @return SearchProfileOverride[] |
542 | */ |
543 | public function listProfileOverrides( $type, $context ) { |
544 | return $this->overriders[$type][$context] ?? []; |
545 | } |
546 | } |