Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.44% covered (success)
97.44%
76 / 78
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SenseIdHtmlFormatter
97.44% covered (success)
97.44%
76 / 78
83.33% covered (warning)
83.33%
5 / 6
12
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 formatEntityId
93.55% covered (success)
93.55%
29 / 31
0.00% covered (danger)
0.00%
0 / 1
6.01
 getTextWrappedInLink
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 buildSenseLinkContents
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 buildGlossMarkup
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
1
 buildLemmasMarkup
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3declare( strict_types = 1 );
4
5namespace Wikibase\Lexeme\Presentation\Formatters;
6
7use InvalidArgumentException;
8use MediaWiki\Html\Html;
9use MediaWiki\Languages\LanguageFactory;
10use MediaWiki\Title\Title;
11use OutOfRangeException;
12use Wikibase\DataModel\Entity\EntityId;
13use Wikibase\DataModel\Services\EntityId\EntityIdFormatter;
14use Wikibase\DataModel\Term\Term;
15use Wikibase\DataModel\Term\TermFallback;
16use Wikibase\DataModel\Term\TermList;
17use Wikibase\Lexeme\Domain\Model\Lexeme;
18use Wikibase\Lexeme\Domain\Model\SenseId;
19use Wikibase\Lib\LanguageFallbackIndicator;
20use Wikibase\Lib\Store\EntityRevisionLookup;
21use Wikibase\Lib\Store\EntityTitleLookup;
22use Wikibase\Lib\Store\RevisionedUnresolvedRedirectException;
23use Wikibase\Lib\TermLanguageFallbackChain;
24use Wikibase\View\LocalizedTextProvider;
25use Wikibase\View\RawMessageParameter;
26
27/**
28 * @license GPL-2.0-or-later
29 */
30class SenseIdHtmlFormatter implements EntityIdFormatter {
31
32    private EntityTitleLookup $titleLookup;
33    private EntityRevisionLookup $revisionLookup;
34    private LocalizedTextProvider $localizedTextProvider;
35    private TermLanguageFallbackChain $termLanguageFallbackChain;
36    private LanguageFallbackIndicator $languageFallbackIndicator;
37    private LanguageFactory $languageFactory;
38    private EntityIdFormatter $entityIdLabelFormatter;
39
40    public function __construct(
41        EntityTitleLookup $titleLookup,
42        EntityRevisionLookup $revisionLookup,
43        LocalizedTextProvider $localizedTextProvider,
44        TermLanguageFallbackChain $termLanguageFallbackChain,
45        LanguageFallbackIndicator $languageFallbackIndicator,
46        LanguageFactory $languageFactory,
47        EntityIdFormatter $entityIdLabelFormatter
48    ) {
49        $this->titleLookup = $titleLookup;
50        $this->revisionLookup = $revisionLookup;
51        $this->localizedTextProvider = $localizedTextProvider;
52        $this->termLanguageFallbackChain = $termLanguageFallbackChain;
53        $this->languageFallbackIndicator = $languageFallbackIndicator;
54        $this->languageFactory = $languageFactory;
55        $this->entityIdLabelFormatter = $entityIdLabelFormatter;
56    }
57
58    /**
59     * @param SenseId $value
60     *
61     * @return string HTML
62     */
63    public function formatEntityId( EntityId $value ): string {
64        if ( !( $value instanceof SenseId ) ) {
65            throw new InvalidArgumentException(
66                'Attempted to format non-Sense entity as Sense: ' . $value->getSerialization() );
67        }
68        $title = $this->titleLookup->getTitleForId( $value );
69
70        try {
71            $lexemeRevision = $this->revisionLookup->getEntityRevision( $value->getLexemeId() );
72        } catch ( RevisionedUnresolvedRedirectException $e ) {
73            $lexemeRevision = null; // see fallback below
74        }
75
76        if ( $lexemeRevision === null ) {
77            return $this->getTextWrappedInLink( $value->getSerialization(), $title );
78        }
79
80        /** @var Lexeme $lexeme */
81        $lexeme = $lexemeRevision->getEntity();
82        '@phan-var Lexeme $lexeme';
83        try {
84            $sense = $lexeme->getSense( $value );
85        } catch ( OutOfRangeException $e ) {
86            return $this->getTextWrappedInLink( $value->getSerialization(), $title );
87        }
88
89        $lexemeLanguageLabel = $this->entityIdLabelFormatter->formatEntityId( $lexeme->getLanguage() );
90        $glossArray = $sense->getGlosses()->toTextArray();
91        $preferredGloss = $this->termLanguageFallbackChain->extractPreferredValue( $glossArray );
92
93        if ( $preferredGloss !== null ) {
94            $languageCode = $this->localizedTextProvider->getLanguageOf(
95                'wikibaselexeme-senseidformatter-layout'
96            );
97            $glossFallback = new TermFallback(
98                $languageCode,
99                $preferredGloss['value'],
100                $preferredGloss['language'],
101                $preferredGloss['source']
102            );
103            $linkContents = $this->buildSenseLinkContents( $lexeme->getLemmas(), $lexemeLanguageLabel, $glossFallback );
104            $suffix = $this->languageFallbackIndicator->getHtml( $glossFallback );
105        } else {
106            $linkContents = $value->getSerialization();
107            $suffix = '';
108        }
109
110        return $this->getTextWrappedInLink( $linkContents, $title ) . $suffix;
111    }
112
113    private function getTextWrappedInLink( string $linkContents, Title $title ): string {
114        return Html::rawElement(
115            'a',
116            [
117                'href' => $title->isLocal() ? $title->getLinkURL() : $title->getFullURL(),
118            ],
119            $linkContents
120        );
121    }
122
123    private function buildSenseLinkContents(
124        TermList $lemmas,
125        string $languageLabel,
126        TermFallback $gloss
127    ): string {
128        return $this->localizedTextProvider->getEscaped(
129            'wikibaselexeme-senseidformatter-layout',
130            [
131                new RawMessageParameter( implode(
132                    $this->localizedTextProvider->getEscaped(
133                        'wikibaselexeme-presentation-lexeme-display-label-separator-multiple-lemma'
134                    ),
135                    $this->buildLemmasMarkup( $lemmas )
136                ) ),
137                new RawMessageParameter( $this->buildGlossMarkup( $gloss ) ),
138                $languageLabel,
139            ]
140        );
141    }
142
143    private function buildGlossMarkup( TermFallback $gloss ): string {
144        $language = $this->languageFactory->getLanguage( $gloss->getActualLanguageCode() );
145        return Html::element(
146            'span',
147            [
148                'lang' => $language->getHtmlCode(),
149                'dir' => $language->getDir(),
150            ],
151            $gloss->getText()
152        );
153    }
154
155    private function buildLemmasMarkup( TermList $lemmas ): array {
156        return array_map( function ( Term $lemma ) {
157            $language = $this->languageFactory->getLanguage( $lemma->getLanguageCode() );
158            return Html::element(
159                'span',
160                [
161                    'lang' => $language->getHtmlCode(),
162                    'dir' => $language->getDir(),
163                ],
164                $lemma->getText()
165            );
166        }, iterator_to_array( $lemmas->getIterator() ) );
167    }
168
169}