Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
105 / 105
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
MediaInfoEntityTermsView
100.00% covered (success)
100.00%
105 / 105
100.00% covered (success)
100.00%
6 / 6
18
100.00% covered (success)
100.00%
1 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 getHtml
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 getCaptionsHeader
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 getLanguagesOrderedByFallbackChain
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
3
 getCaptionsContent
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
6
 getSingleCaptionLayout
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
3
 getCaptionElementForLanguage
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace Wikibase\MediaInfo\View;
4
5use MediaWiki\Html\Html;
6use MediaWiki\Output\OutputPage;
7use OOUI\Element;
8use OOUI\HorizontalLayout;
9use OOUI\LabelWidget;
10use OOUI\PanelLayout;
11use OOUI\Tag;
12use Wikibase\DataModel\Term\TermList;
13use Wikibase\Lib\LanguageNameLookup;
14use Wikibase\Lib\TermLanguageFallbackChain;
15use Wikibase\MediaInfo\DataModel\MediaInfo;
16use Wikibase\View\LanguageDirectionalityLookup;
17use Wikibase\View\LocalizedTextProvider;
18
19/**
20 * Generates HTML to display the terms of a MediaInfo entity
21 *
22 * @license GPL-2.0-or-later
23 */
24class MediaInfoEntityTermsView {
25
26    /**
27     * @var LanguageNameLookup
28     */
29    private $languageNameLookup;
30
31    /**
32     * @var LanguageDirectionalityLookup
33     */
34    private $languageDirectionalityLookup;
35
36    /**
37     * @var LocalizedTextProvider
38     */
39    private $textProvider;
40
41    /**
42     * @var TermLanguageFallbackChain
43     */
44    private $termFallbackChain;
45
46    public const CAPTIONS_CUSTOM_TAG = 'mediaInfoViewCaptions';
47    public const CAPTION_EMPTY_CLASS = 'wbmi-entityview-emptyCaption';
48    public const HIDEABLE_CAPTION_CLASS = 'wbmi-entityview-hideable';
49    private const CAPTIONS_CONTAINER = 'wbmi-entityview-captionsPanel';
50
51    /**
52     * @param LanguageNameLookup $languageNameLookup
53     * @param LanguageDirectionalityLookup $languageDirectionalityLookup
54     * @param LocalizedTextProvider $textProvider
55     * @param TermLanguageFallbackChain $termFallbackChain
56     * @codeCoverageIgnore
57     */
58    public function __construct(
59        LanguageNameLookup $languageNameLookup,
60        LanguageDirectionalityLookup $languageDirectionalityLookup,
61        LocalizedTextProvider $textProvider,
62        TermLanguageFallbackChain $termFallbackChain
63    ) {
64        OutputPage::setupOOUI();
65
66        $this->languageNameLookup = $languageNameLookup;
67        $this->languageDirectionalityLookup = $languageDirectionalityLookup;
68        $this->textProvider = $textProvider;
69        $this->termFallbackChain = $termFallbackChain;
70    }
71
72    /**
73     * @param MediaInfo $entity
74     *
75     * @return string HTML
76     */
77    public function getHtml(
78        MediaInfo $entity
79    ) {
80        $layout = new PanelLayout( [
81            'classes' => [ self::CAPTIONS_CONTAINER ],
82            'scrollable' => false,
83            'padded' => false,
84            'expanded' => false,
85            'framed' => true,
86        ] );
87        $layout->appendContent( $this->getCaptionsHeader() );
88        $layout->appendContent(
89            $this->getCaptionsContent(
90                $entity->getLabels(),
91                $this->getLanguagesOrderedByFallbackChain( $entity )
92            )
93        );
94        $html = $layout->toString();
95
96        // Wrap the whole thing in a custom tag so we can manipulate its position on the page
97        // later on
98        return Html::rawElement(
99            self::CAPTIONS_CUSTOM_TAG,
100            [],
101            $html
102        );
103    }
104
105    private function getCaptionsHeader() {
106        $header = new Tag( 'h3' );
107        $header->addClasses( [ 'wbmi-entityview-captions-header' ] );
108        $header->appendContent(
109            $this->textProvider->get(
110                'wikibasemediainfo-entitytermsforlanguagelistview-caption'
111            )
112        );
113        return $header;
114    }
115
116    /**
117     * Return the language codes for labels, in the following order:
118     *
119     * - the first language in the fallback chain (i.e. the interface language), whether or not
120     *      a label exists in that language
121     * - the rest of the languages in the fallback chain for which a label exists, in order
122     * - all other languages for which a label exists (in any order)
123     *
124     * @param MediaInfo $entity
125     * @return array
126     */
127    private function getLanguagesOrderedByFallbackChain( MediaInfo $entity ) {
128        $labelLanguages = array_keys( $entity->getLabels()->toTextArray() );
129        $fbChainLanguages = $this->termFallbackChain->getFetchLanguageCodes();
130        $orderedLangCodes =
131            array_values(
132                array_flip(
133                    array_merge(
134                        array_flip(
135                            array_intersect(
136                                $this->termFallbackChain->getFetchLanguageCodes(),
137                                $labelLanguages
138                            )
139                        ),
140                        array_flip( $labelLanguages )
141                    )
142                )
143            );
144        if (
145            count( $fbChainLanguages ) > 0 &&
146            !in_array( $fbChainLanguages[0], $orderedLangCodes )
147        ) {
148            array_unshift( $orderedLangCodes, $fbChainLanguages[0] );
149        }
150        return $orderedLangCodes;
151    }
152
153    /**
154     * The following captions are always shown
155     *
156     * - the first caption
157     * - the first non-empty caption in the fallback chain IF AND ONLY IF the first caption has no
158     *   value
159     *
160     * @param TermList $captions
161     * @param string[] $languageCodes
162     * @return Tag[]
163     */
164    private function getCaptionsContent(
165        TermList $captions,
166        array $languageCodes
167    ) {
168        $captionLayouts = [];
169
170        $firstCaptionHasNoValue = false;
171        foreach ( $languageCodes as $index => $languageCode ) {
172            if ( $index == 0 ) {
173                $showCaption = true;
174                $firstCaptionHasNoValue = !$captions->hasTermForLanguage( $languageCode );
175            } elseif (
176                $index == 1 &&
177                $firstCaptionHasNoValue &&
178                in_array( $languageCode, $this->termFallbackChain->getFetchLanguageCodes() )
179            ) {
180                $showCaption = true;
181            } else {
182                $showCaption = false;
183            }
184            $captionLayouts[] = $this->getSingleCaptionLayout(
185                $captions,
186                $languageCode,
187                $showCaption
188            );
189        }
190
191        return $captionLayouts;
192    }
193
194    /**
195     * @param TermList $labels
196     * @param string $languageCode
197     * @param bool $showCaption Hide the label (via styling) if false
198     *
199     * @return HorizontalLayout
200     */
201    public function getSingleCaptionLayout(
202        TermList $labels,
203        $languageCode,
204        $showCaption
205    ) {
206        $languageName = $this->languageNameLookup->getName( $languageCode );
207
208        // This label should be shown in the UI direction rather than the
209        // language's direction since it's not editable.
210        $languageElement = new LabelWidget( [
211            'label' => $languageName,
212            'classes' => [ 'wbmi-language-label' ]
213        ] );
214
215        $captionElement = $this->getCaptionElementForLanguage(
216            $labels,
217            $languageCode
218        );
219
220        $classes = [ 'wbmi-entityview-caption' ];
221        if ( !$showCaption ) {
222            $classes[] = self::HIDEABLE_CAPTION_CLASS;
223        }
224        $layout = new HorizontalLayout( [
225            'items' => [
226                $languageElement,
227                $captionElement,
228            ],
229            'classes' => $classes,
230        ] );
231        if ( !$showCaption ) {
232            $layout->setAttributes( [
233                'style' => 'display:none;',
234            ] );
235        }
236        return $layout;
237    }
238
239    private function getCaptionElementForLanguage( TermList $termList, $languageCode ) {
240        $classes = [ 'wbmi-caption-value' ];
241        try {
242            $captionText = $termList->getByLanguage( $languageCode )->getText();
243        } catch ( \OutOfBoundsException $e ) {
244            $captionText = htmlspecialchars(
245                $this->textProvider->get( 'wikibasemediainfo-filepage-caption-empty' )
246            );
247            $classes[] = self::CAPTION_EMPTY_CLASS;
248        }
249
250        $captionElement = new Element( [
251            'content' => $captionText,
252            'classes' => $classes,
253        ] );
254        $captionElement->setAttributes( [
255            'lang' => $languageCode,
256            'dir' =>
257                $this->languageDirectionalityLookup->getDirectionality( $languageCode ) ?: 'auto',
258        ] );
259        return $captionElement;
260    }
261
262}