Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
ResultBuilder
100.00% covered (success)
100.00%
53 / 53
100.00% covered (success)
100.00%
5 / 5
18
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 createResultArray
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 buildEntry
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
7
 checkAndSetAlias
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 mergeWithTraditionalSearchResults
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
1<?php
2
3namespace PropertySuggester;
4
5use MediaWiki\Api\ApiResult;
6use PropertySuggester\Suggesters\Suggestion;
7use Wikibase\DataAccess\PrefetchingTermLookup;
8use Wikibase\Lib\LanguageFallbackChainFactory;
9use Wikibase\Lib\Store\EntityTitleLookup;
10use 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 */
18class 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}