Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
SpecialListMissingLabels
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 9
306
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getGroupName
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getDescription
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isListed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 userCanExecute
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 getParameters
0.00% covered (danger)
0.00%
0 / 14
0.00% covered (danger)
0.00%
0 / 1
56
 execute
0.00% covered (danger)
0.00%
0 / 32
0.00% covered (danger)
0.00%
0 / 1
6
 getHeaderTitle
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getHeaderForm
0.00% covered (danger)
0.00%
0 / 26
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2/**
3 * WikiLambda Special:ListMissingLabels page
4 *
5 * @file
6 * @ingroup Extensions
7 * @copyright 2020– Abstract Wikipedia team; see AUTHORS.txt
8 * @license MIT
9 */
10
11namespace MediaWiki\Extension\WikiLambda\Special;
12
13use MediaWiki\Extension\WikiLambda\Fields\HTMLZLanguageSelectField;
14use MediaWiki\Extension\WikiLambda\Fields\HTMLZTypeSelectField;
15use MediaWiki\Extension\WikiLambda\Pagers\BasicZObjectPager;
16use MediaWiki\Extension\WikiLambda\Registry\ZLangRegistry;
17use MediaWiki\Extension\WikiLambda\Registry\ZTypeRegistry;
18use MediaWiki\Extension\WikiLambda\ZObjectStore;
19use MediaWiki\Extension\WikiLambda\ZObjectUtils;
20use MediaWiki\HTMLForm\HTMLForm;
21use MediaWiki\Language\LanguageFallback;
22use MediaWiki\SpecialPage\SpecialPage;
23use MediaWiki\User\User;
24
25class SpecialListMissingLabels extends SpecialPage {
26
27    private ZLangRegistry $langRegistry;
28
29    /**
30     * @param ZObjectStore $zObjectStore
31     * @param LanguageFallback $languageFallback
32     */
33    public function __construct(
34        private readonly ZObjectStore $zObjectStore,
35        private readonly LanguageFallback $languageFallback
36    ) {
37        parent::__construct( 'ListMissingLabels' );
38
39        $this->langRegistry = ZLangRegistry::singleton();
40    }
41
42    /**
43     * @inheritDoc
44     */
45    protected function getGroupName() {
46        // Triggers use of message specialpages-group-wikilambda
47        return 'wikilambda';
48    }
49
50    /**
51     * @inheritDoc
52     */
53    public function getDescription() {
54        return $this->msg( 'wikilambda-special-missinglabels' );
55    }
56
57    /** @inheritDoc */
58    public function isListed() {
59        // No usage allowed on client-mode wikis.
60        return $this->getConfig()->get( 'WikiLambdaEnableRepoMode' );
61    }
62
63    /**
64     * @inheritDoc
65     *
66     * @param User $user
67     * @return bool
68     */
69    public function userCanExecute( User $user ) {
70        if ( !$this->getConfig()->get( 'WikiLambdaEnableRepoMode' ) ) {
71            // No usage allowed on client-mode wikis.
72            return false;
73        }
74        return parent::userCanExecute( $user );
75    }
76
77    /**
78     * Get and validate SpecialPage parameters from subpage and url
79     *
80     * @param string $subpage
81     * @return array - type ZID, lang ZID, excludePreDefined
82     */
83    private function getParameters( $subpage ) {
84        // Get language from Request.
85        // * Can be empty string, valid or invalid Zid
86        // * Default: User language
87        $langZid = $this->getRequest()->getText( 'language' );
88        if ( ( !$langZid ) || !ZObjectUtils::isValidZObjectReference( $langZid ) ) {
89            $langCode = $this->getLanguage()->getCode();
90            $langZid = $this->langRegistry->getLanguageZidFromCode(
91                $langCode,
92                // Fallback to English if the user language is not a valid ZLanguage
93                true
94            );
95        }
96
97        // Get type from subpage; overwrite with value from Request.
98        // * subpage can be empty string or NULL or valid/invalid string
99        // * requestType can be empty string or valid/invalid string
100        // Default: Z8/Function
101        $requestType = $this->getRequest()->getText( 'type' );
102        if ( $requestType && ZObjectUtils::isValidZObjectReference( $requestType ) ) {
103            $type = $requestType;
104        } else {
105            $type = $subpage && ZObjectUtils::isValidZObjectReference( $subpage ) ?
106                $subpage : ZTypeRegistry::Z_FUNCTION;
107        }
108
109        $excludePreDefined = $this->getRequest()->getBool( 'excludePreDefined' );
110
111        return [ $type, $langZid, $excludePreDefined ];
112    }
113
114    /**
115     * @inheritDoc
116     */
117    public function execute( $subpage ) {
118        if ( !$this->userCanExecute( $this->getUser() ) ) {
119            $this->displayRestrictionError();
120        }
121
122        // Get and validate page parameters
123        [ $type, $langZid, $excludePreDefined ] = $this->getParameters( $subpage );
124
125        // Set headers
126        $this->setHeaders();
127        $this->outputHeader( 'wikilambda-special-missinglabels-summary' );
128
129        // Set output
130        $output = $this->getOutput();
131        $output->enableOOUI();
132        $output->addModuleStyles( [ 'mediawiki.special' ] );
133
134        // TODO (T300519): Make this help page.
135        $this->addHelpLink( 'Help:Wikifunctions/Missing labels' );
136
137        // Get list of fallback language Zids
138        $languageZids = $this->langRegistry->getListOfFallbackLanguageZids(
139            $this->languageFallback,
140            $this->getLanguage()->getCode()
141        );
142
143        // Add selected language at the start of the array; this way
144        // we will identify objects with missing label in langZid by
145        // checking that the preferred label is not in langZid.
146        // The label displayed in the list will be then the one in the
147        // user language or its closes fallback. Also, remove duplicates
148        // in case the selected language is the same as user language.
149        array_unshift( $languageZids, $langZid );
150        $languageZids = array_unique( $languageZids );
151
152        // Build BasicZObjectPager for the given filters
153        $filters = [
154            'type' => $type,
155            'missing_language' => $langZid
156        ];
157        $pager = new BasicZObjectPager(
158            $this->getContext(),
159            $this->zObjectStore,
160            $languageZids,
161            null,
162            $excludePreDefined,
163            $filters
164        );
165
166        // Add the header form
167        $output->addHTML( $this->getHeaderForm( $type, $langZid ) );
168
169        // Add the top pagination controls
170        $output->addHTML( $pager->getNavigationBar() );
171        // Add the item list body
172        $output->addWikiTextAsInterface( $pager->getBody() );
173        // Add the bottom pagination controls
174        $output->addHTML( $pager->getNavigationBar() );
175
176        // Add bottom pagination controls
177        $output->addWikiTextAsInterface( $pager->getBottomLinks() );
178    }
179
180    /**
181     * Render the header for listing ZObjects by a specific type
182     * with missing labels in the given language.
183     *
184     * @param string $typeZid - The type of ZObjects being listed.
185     * @param string $langZid - The selected language Zid.
186     * @return string - The text for the header.
187     */
188    private function getHeaderTitle( $typeZid, $langZid ) {
189        $typeLabel = $this->zObjectStore->fetchZObjectLabel( $typeZid, $this->getLanguage()->getCode() );
190        $langLabel = $this->zObjectStore->fetchZObjectLabel( $langZid, $this->getLanguage()->getCode() );
191        return $this->msg( 'wikilambda-special-missinglabels-for-type' )
192            ->rawParams(
193                htmlspecialchars( $typeLabel ?? '' ),
194                htmlspecialchars( $typeZid ?? '' ),
195                htmlspecialchars( $langLabel ?? '' )
196            )
197            ->text();
198    }
199
200    /**
201     * Build the form to place at the head of the Special page,
202     * with a selector field for ZTypes and another for ZLanguages.
203     *
204     * @param string $typeZid
205     * @param string $langZid
206     * @return string
207     */
208    public function getHeaderForm( $typeZid, $langZid ) {
209        $formHeader = $this->getHeaderTitle( $typeZid, $langZid );
210        $formDescriptor = [
211            'type' => [
212                'label' => 'Type',
213                'class' => HTMLZTypeSelectField::class,
214                'name' => 'type',
215                'default' => $typeZid
216            ],
217            'language' => [
218                'label' => 'Language',
219                'class' => HTMLZLanguageSelectField::class,
220                'name' => 'language',
221                'default' => $langZid
222            ],
223            'excludePreDefined' => [
224                'type' => 'check',
225                'label' => $this->msg( 'wikilambda-special-objectsbytype-form-excludepredefined' )->text(),
226                'name' => 'excludePreDefined',
227                'default' => false
228            ]
229        ];
230
231        $htmlForm = HTMLForm::factory( 'ooui', $formDescriptor, $this->getContext() )
232            ->setWrapperLegend( $formHeader )
233            ->setCollapsibleOptions( true )
234            ->setMethod( 'get' );
235        return $htmlForm->prepareForm()->getHTML( false );
236    }
237}