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