Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
53 / 53 |
|
100.00% |
5 / 5 |
CRAP | |
100.00% |
1 / 1 |
| ResultBuilder | |
100.00% |
53 / 53 |
|
100.00% |
5 / 5 |
18 | |
100.00% |
1 / 1 |
| __construct | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
| createResultArray | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
| buildEntry | |
100.00% |
20 / 20 |
|
100.00% |
1 / 1 |
7 | |||
| checkAndSetAlias | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
| mergeWithTraditionalSearchResults | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
| 1 | <?php |
| 2 | |
| 3 | namespace PropertySuggester; |
| 4 | |
| 5 | use MediaWiki\Api\ApiResult; |
| 6 | use PropertySuggester\Suggesters\Suggestion; |
| 7 | use Wikibase\DataAccess\PrefetchingTermLookup; |
| 8 | use Wikibase\Lib\LanguageFallbackChainFactory; |
| 9 | use Wikibase\Lib\Store\EntityTitleLookup; |
| 10 | use Wikibase\Lib\Store\LanguageFallbackLabelDescriptionLookup; |
| 11 | |
| 12 | /** |
| 13 | * ResultBuilder builds Json-compatible array structure from suggestions |
| 14 | * |
| 15 | * @author BP2013N2 |
| 16 | * @license GPL-2.0-or-later |
| 17 | */ |
| 18 | class ResultBuilder { |
| 19 | |
| 20 | /** |
| 21 | * @var string |
| 22 | */ |
| 23 | private $searchPattern; |
| 24 | |
| 25 | /** |
| 26 | * @param ApiResult $result |
| 27 | * @param PrefetchingTermLookup $prefetchingTermLookup |
| 28 | * @param LanguageFallbackChainFactory $languageFallbackChainFactory |
| 29 | * @param EntityTitleLookup $entityTitleLookup |
| 30 | * @param string $search |
| 31 | */ |
| 32 | public function __construct( |
| 33 | private readonly ApiResult $result, |
| 34 | private readonly PrefetchingTermLookup $prefetchingTermLookup, |
| 35 | private readonly LanguageFallbackChainFactory $languageFallbackChainFactory, |
| 36 | private readonly EntityTitleLookup $entityTitleLookup, |
| 37 | $search |
| 38 | ) { |
| 39 | $this->searchPattern = '/^' . preg_quote( $search, '/' ) . '/i'; |
| 40 | } |
| 41 | |
| 42 | public function createResultArray( array $suggestions, string $language ): array { |
| 43 | $ids = []; |
| 44 | foreach ( $suggestions as $suggestion ) { |
| 45 | $ids[] = $suggestion->getPropertyId(); |
| 46 | } |
| 47 | $fallbackChain = $this->languageFallbackChainFactory->newFromLanguageCode( $language ); |
| 48 | |
| 49 | $this->prefetchingTermLookup->prefetchTerms( |
| 50 | $ids, |
| 51 | [ 'label', 'description', 'alias' ], |
| 52 | $fallbackChain->getFetchLanguageCodes() |
| 53 | ); |
| 54 | |
| 55 | $fallbackTermLookup = new LanguageFallbackLabelDescriptionLookup( |
| 56 | $this->prefetchingTermLookup, |
| 57 | $fallbackChain |
| 58 | ); |
| 59 | return array_map( function ( Suggestion $suggestion ) use ( $language, $fallbackTermLookup ) { |
| 60 | return $this->buildEntry( $suggestion, $language, $fallbackTermLookup ); |
| 61 | }, $suggestions ); |
| 62 | } |
| 63 | |
| 64 | /** |
| 65 | * @param Suggestion $suggestion |
| 66 | * @param string $language |
| 67 | * @param LanguageFallbackLabelDescriptionLookup $fallbackTermLookup |
| 68 | * |
| 69 | * @return array |
| 70 | */ |
| 71 | private function buildEntry( |
| 72 | Suggestion $suggestion, |
| 73 | string $language, |
| 74 | LanguageFallbackLabelDescriptionLookup $fallbackTermLookup |
| 75 | ): array { |
| 76 | $id = $suggestion->getPropertyId(); |
| 77 | $entry = [ |
| 78 | 'id' => $id->getSerialization(), |
| 79 | 'url' => $this->entityTitleLookup->getTitleForId( $id )->getFullURL(), |
| 80 | 'rating' => $suggestion->getProbability(), |
| 81 | ]; |
| 82 | |
| 83 | $label = $fallbackTermLookup->getLabel( $id ); |
| 84 | if ( $label !== null ) { |
| 85 | $entry['label'] = $label->getText(); |
| 86 | } |
| 87 | |
| 88 | $description = $fallbackTermLookup->getDescription( $id ); |
| 89 | if ( $description !== null ) { |
| 90 | $entry['description'] = $description->getText(); |
| 91 | } |
| 92 | |
| 93 | $aliases = $this->prefetchingTermLookup->getPrefetchedAliases( $id, $language ); |
| 94 | if ( is_array( $aliases ) && $aliases ) { |
| 95 | $this->checkAndSetAlias( $entry, $aliases[0] ); |
| 96 | } |
| 97 | |
| 98 | if ( !isset( $entry['label'] ) ) { |
| 99 | $entry['label'] = $id->getSerialization(); |
| 100 | } elseif ( preg_match( $this->searchPattern, $entry['label'] ) ) { |
| 101 | // No aliases needed in the output when the label already is a successful match. |
| 102 | unset( $entry['aliases'] ); |
| 103 | } |
| 104 | |
| 105 | return $entry; |
| 106 | } |
| 107 | |
| 108 | /** |
| 109 | * @param array &$entry |
| 110 | * @param string $alias |
| 111 | */ |
| 112 | private function checkAndSetAlias( array &$entry, $alias ) { |
| 113 | if ( preg_match( $this->searchPattern, $alias ) ) { |
| 114 | if ( !isset( $entry['aliases'] ) ) { |
| 115 | $entry['aliases'] = []; |
| 116 | ApiResult::setIndexedTagName( $entry['aliases'], 'alias' ); |
| 117 | } |
| 118 | $entry['aliases'][] = $alias; |
| 119 | } |
| 120 | } |
| 121 | |
| 122 | /** |
| 123 | * @param array[] $entries |
| 124 | * @param array[] $searchResults |
| 125 | * @param int $resultSize |
| 126 | * @return array[] representing Json |
| 127 | */ |
| 128 | public function mergeWithTraditionalSearchResults( |
| 129 | array $entries, |
| 130 | array $searchResults, |
| 131 | $resultSize |
| 132 | ) { |
| 133 | // Avoid duplicates |
| 134 | $existingKeys = []; |
| 135 | foreach ( $entries as $entry ) { |
| 136 | $existingKeys[$entry['id']] = true; |
| 137 | } |
| 138 | |
| 139 | $distinctCount = count( $entries ); |
| 140 | foreach ( $searchResults as $result ) { |
| 141 | if ( !array_key_exists( $result['id'], $existingKeys ) ) { |
| 142 | $entries[] = $result; |
| 143 | $distinctCount++; |
| 144 | if ( $distinctCount >= $resultSize ) { |
| 145 | break; |
| 146 | } |
| 147 | } |
| 148 | } |
| 149 | return $entries; |
| 150 | } |
| 151 | |
| 152 | } |