Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
61.90% covered (warning)
61.90%
78 / 126
42.86% covered (danger)
42.86%
6 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProofreadPageLuaLibrary
61.90% covered (warning)
61.90%
78 / 126
42.86% covered (danger)
42.86%
6 / 14
71.34
0.00% covered (danger)
0.00%
0 / 1
 register
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
2
 addTemplateDependencyOnPage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 addTemplateDependencyOnAllPagesInIndex
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 doGetIndexProgress
0.00% covered (danger)
0.00%
0 / 17
0.00% covered (danger)
0.00%
0 / 1
6
 getIndexContent
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
6
 doGetIndexFields
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
2
 doGetIndexCategories
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getPaginationForIndex
66.67% covered (warning)
66.67%
4 / 6
0.00% covered (danger)
0.00%
0 / 1
2.15
 doGetNumberOfPages
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 doGetPageInIndex
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 doGetPageQuality
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
2.02
 getIndexForPage
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 doGetIndexForPage
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 doGetPageNumbering
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3namespace ProofreadPage;
4
5use MediaWiki\Content\WikitextContent;
6use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LibraryBase;
7use MediaWiki\Extension\Scribunto\Engines\LuaCommon\LuaError;
8use MediaWiki\Logger\LoggerFactory;
9use MediaWiki\Parser\ParserOutput;
10use MediaWiki\Title\Title;
11use OutOfBoundsException;
12use ProofreadPage\Index\IndexContent;
13use ProofreadPage\Page\PageLevel;
14use ProofreadPage\Pagination\Pagination;
15use Psr\Log\LoggerInterface;
16
17class ProofreadPageLuaLibrary extends LibraryBase {
18
19    /**
20     * @var Context
21     */
22    private $context;
23
24    /**
25     * @var LoggerInterface
26     */
27    private $logger;
28
29    /**
30     * @var ParserOutput|null
31     */
32    private $parserOutput;
33
34    /** @inheritDoc */
35    public function register() {
36        $this->context = Context::getDefaultContext();
37
38        $extensionLuaPath = __DIR__ . '/lualib/ProofreadPage.lua';
39
40        // "export" PHP functions to the Lua library interface
41        $lib = [
42            'doGetIndexProgress' => [ $this, 'doGetIndexProgress' ],
43            'doGetIndexFields' => [ $this, 'doGetIndexFields' ],
44            'doGetIndexCategories' => [ $this, 'doGetIndexCategories' ],
45            'doGetNumberOfPages' => [ $this, 'doGetNumberOfPages' ],
46            'doGetPageInIndex' => [ $this, 'doGetPageInIndex' ],
47            'doGetIndexPageNumbers' => [ $this, 'doGetIndexPageNumbers' ],
48            'doGetPageQuality' => [ $this, 'doGetPageQuality' ],
49            'doGetIndexForPage' => [ $this, 'doGetIndexForPage' ],
50            'doGetPageNumbering' => [ $this, 'doGetPageNumbering' ]
51        ];
52        $opts = [
53            'NS_INDEX' => $this->context->getIndexNamespaceId(),
54            'NS_PAGE' => $this->context->getPageNamespaceId(),
55            'qualityLevel' => [
56                'WITHOUT_TEXT' => PageLevel::WITHOUT_TEXT,
57                'NOT_PROOFREAD' => PageLevel::NOT_PROOFREAD,
58                'PROBLEMATIC' => PageLevel::PROBLEMATIC,
59                'PROOFREAD' => PageLevel::PROOFREAD,
60                'VALIDATED' => PageLevel::VALIDATED,
61            ]
62        ];
63
64        $this->logger = LoggerFactory::getInstance( 'ext.proofreadPage.lua' );
65
66        if ( $this->getParser() ) {
67            $this->parserOutput = $this->getParser()->getOutput();
68        }
69
70        return $this->getEngine()->registerInterface( $extensionLuaPath, $lib, $opts );
71    }
72
73    /**
74     * Add a parser dependency on the given page (index or otherwise)
75     *
76     * @param Title|null $pageTitle
77     */
78    private function addTemplateDependencyOnPage( ?Title $pageTitle ) {
79        if ( $this->parserOutput && $pageTitle ) {
80            $this->parserOutput->addTemplate(
81                $pageTitle,
82                $pageTitle->getArticleID(),
83                $pageTitle->getLatestRevID()
84            );
85        }
86    }
87
88    /**
89     * Add a parser dependency on every page in the index (and the index itself)
90     *
91     * @param Title|null $indexTitle
92     */
93    private function addTemplateDependencyOnAllPagesInIndex( ?Title $indexTitle ) {
94        if ( $this->parserOutput && $indexTitle ) {
95            // this depends on the index itself (for the content)
96            $pagination = $this->getPaginationForIndex( $indexTitle );
97            $pagination->prefetchPageLinks();
98
99            foreach ( $pagination as $pageTitle ) {
100                $this->addTemplateDependencyOnPage( $pageTitle );
101            }
102        }
103    }
104
105    /**
106     * Return the index statistics for the given index name
107     *
108     * This function may be expensive if the index has not yet been cached.
109     *
110     * @param string $indexName The index title to get stats for
111     * @return array The result table, in an array
112     * @throws LuaError If the expensive function count has been exceeded
113     */
114    public function doGetIndexProgress( string $indexName ): array {
115        $indexTitle = Title::makeTitleSafe( $this->context->getIndexNamespaceId(), $indexName );
116
117        $statsLookup = $this->context->getIndexQualityStatsLookup();
118
119        if ( !$statsLookup->isIndexTitleInCache( $indexTitle ) ) {
120            $this->logger->debug( "Index stats cache miss: " . $indexTitle->getFullText() );
121            $this->incrementExpensiveFunctionCount();
122        }
123
124        // Progress depends on every page in the index
125        $this->addTemplateDependencyOnAllPagesInIndex( $indexTitle );
126
127        $indexStats = $statsLookup->getStatsForIndexTitle( $indexTitle );
128
129        // Map stats to the Lua table
130        return [ [
131            0 => $indexStats->getNumberOfPagesForQualityLevel( 0 ),
132            1 => $indexStats->getNumberOfPagesForQualityLevel( 1 ),
133            2 => $indexStats->getNumberOfPagesForQualityLevel( 2 ),
134            3 => $indexStats->getNumberOfPagesForQualityLevel( 3 ),
135            4 => $indexStats->getNumberOfPagesForQualityLevel( 4 ),
136            "total" => $indexStats->getNumberOfPages(),
137            "existing" => $indexStats->getNumberOfPagesWithAnyQualityLevel(),
138            "missing" => $indexStats->getNumberOfPagesWithoutQualityLevel(),
139        ] ];
140    }
141
142    /**
143     * Get the IndexContent for a give index
144     * @param string $indexName the name of the index
145     * @return IndexContent|null the index content (or null if the index is
146     *  not found or the title construction fails)
147     */
148    private function getIndexContent( string $indexName ): ?IndexContent {
149        $indexTitle = Title::makeTitleSafe( $this->context->getIndexNamespaceId(), $indexName );
150        $contentLookup = $this->context->getIndexContentLookup();
151
152        if ( !$contentLookup->isIndexTitleInCache( $indexTitle ) ) {
153            $this->logger->debug( "Index content cache miss: " . $indexTitle->getFullText() );
154            $this->incrementExpensiveFunctionCount();
155        }
156
157        // if the index content is needed, there's a dependency on the index
158        $this->addTemplateDependencyOnPage( $indexTitle );
159
160        return $contentLookup->getIndexContentForTitle( $indexTitle );
161    }
162
163    /**
164     * Return the index fields for the given index name
165     *
166     * This function may be expensive if the index content has not yet been cached.
167     *
168     * @param string $indexName the index title to get stats for
169     * @return array the result table, in an array
170     * @throws LuaError If the expensive function count has been exceeded
171     */
172    public function doGetIndexFields( string $indexName ): array {
173        // this can be expensive
174        $indexContent = $this->getIndexContent( $indexName );
175
176        $textConverter = static function ( WikitextContent $field ): string {
177            return $field->getText();
178        };
179
180        return [ array_map( $textConverter, $indexContent->getFields() ) ];
181    }
182
183    /**
184     * Return the index categories for the given index name
185     *
186     * Note this is only the categories entered on the index page, and doesn't
187     * include categories added by the Index page template or any other
188     * expansion of wikitext.
189     *
190     * This function may be expensive if the index content has not yet been cached.
191     *
192     * @param string $indexName The index title to get stats for
193     * @return array The result table, in an array
194     * @throws LuaError If the expensive function count has been exceeded
195     */
196    public function doGetIndexCategories( string $indexName ): array {
197        // this can be expensive
198        $indexContent = $this->getIndexContent( $indexName );
199
200        $textConverter = static function ( Title $field ): string {
201            return $field->getText();
202        };
203
204        // remap into a Lua-esque 1-indexed array of title strings
205        return [
206            array_map( $textConverter, $indexContent->getCategories() )
207        ];
208    }
209
210    /**
211     * Get the Pagination for a given index
212     *
213     * Increments the expensive function counter if needed
214     *
215     * @param Title $indexTitle
216     * @return Pagination
217     */
218    private function getPaginationForIndex( Title $indexTitle ) {
219        $paginationFactory = $this->context->getPaginationFactory();
220
221        if ( !$paginationFactory->isIndexTitleInCache( $indexTitle ) ) {
222            $this->logger->debug( "Index pagination cache miss: " . $indexTitle->getFullText() );
223            $this->incrementExpensiveFunctionCount();
224        }
225
226        // the pagination depends on the index content
227        $this->addTemplateDependencyOnPage( $indexTitle );
228
229        // maybe expensive, but cached
230        return $paginationFactory->getPaginationForIndexTitle( $indexTitle );
231    }
232
233    /**
234     * Get the total number of pages in the index
235     *
236     * @param string $indexName The index title
237     * @return array The number of pages in the index, 0 for an invalid index
238     */
239    public function doGetNumberOfPages( string $indexName ): array {
240        $indexTitle = Title::makeTitleSafe( $this->context->getIndexNamespaceId(), $indexName );
241
242        // maybe expensive
243        $pagination = $this->getPaginationForIndex( $indexTitle );
244
245        return [ $pagination->getNumberOfPages() ];
246    }
247
248    /**
249     * The n'th page in the pagination for an index
250     *
251     * @param string $indexName The index title
252     * @param int $n The index of the pag in that index (1 is the first)
253     * @return array The page title, as an array for Lua
254     */
255    public function doGetPageInIndex( string $indexName, int $n ): array {
256        $indexTitle = Title::makeTitleSafe( $this->context->getIndexNamespaceId(), $indexName );
257
258        // maybe expensive
259        $pagination = $this->getPaginationForIndex( $indexTitle );
260
261        try {
262            $pageTitle = $pagination->getPageTitle( $n );
263        } catch ( OutOfBoundsException $e ) {
264            return [ null ];
265        }
266
267        return [ $pageTitle->getText() ];
268    }
269
270    /**
271     * Get the quality information for a given page
272     *
273     * @param string $pageName The title of the page to get the info for
274     * @return array The quality information as an array
275     */
276    public function doGetPageQuality( string $pageName ): array {
277        $pageTitle = Title::makeTitleSafe( $this->context->getPageNamespaceId(), $pageName );
278        $pqLookup = $this->context->getPageQualityLevelLookup();
279
280        if ( !$pqLookup->isPageTitleInCache( $pageTitle ) ) {
281            $this->logger->debug( "Page quality cache miss: " . $pageTitle->getFullText() );
282            $this->incrementExpensiveFunctionCount();
283        }
284
285        // the page quality depends only on that page
286        $this->addTemplateDependencyOnPage( $pageTitle );
287
288        return [ [
289            'level' => $pqLookup->getQualityLevelForPageTitle( $pageTitle ),
290            // 'user' => $pageLevel->getUser(), // T289137
291            // 'timestamp' maybe?
292        ] ];
293    }
294
295    /**
296     * Get the title of the index for a given page
297     *
298     * @param Title $pageTitle
299     * @return Title|null The title of the index
300     */
301    private function getIndexForPage( Title $pageTitle ): ?Title {
302        $ifpLookup = $this->context->getIndexForPageLookup();
303
304        if ( !$ifpLookup->isPageTitleInCache( $pageTitle ) ) {
305            $this->logger->debug( "Index for page cache miss: " . $pageTitle->getFullText() );
306            $this->incrementExpensiveFunctionCount();
307        }
308
309        return $ifpLookup->getIndexForPageTitle( $pageTitle );
310    }
311
312    /**
313     * Get the title of the index for a given page
314     *
315     * @param string $pageName The title of the page to get the index for
316     * @return array The name of the index
317     */
318    public function doGetIndexForPage( string $pageName ): array {
319        $pageTitle = Title::makeTitleSafe( $this->context->getPageNamespaceId(), $pageName );
320        $indexTitle = $this->getIndexForPage( $pageTitle );
321
322        // if the page is moved, this could change, and also if the index pagination changes
323        $this->addTemplateDependencyOnPage( $pageTitle );
324        $this->addTemplateDependencyOnPage( $indexTitle );
325
326        if ( $indexTitle == null ) {
327            return [ null ];
328        }
329
330        return [ $indexTitle->getBaseText() ];
331    }
332
333    /**
334     * Get the page numbering for a given page
335     *
336     * @param string $pageName The title of the page to get the numbering for
337     * @return array The name of the index
338     */
339    public function doGetPageNumbering( string $pageName ): array {
340        $pageTitle = Title::makeTitleSafe( $this->context->getPageNamespaceId(), $pageName );
341
342        // maybe expensive
343        $indexTitle = $this->getIndexForPage( $pageTitle );
344
345        if ( $indexTitle == null ) {
346            return [ null ];
347        }
348
349        // this is a cached lookup, so we'll only look this indexes pagination up once,
350        // but that first time is expensive
351        $pagination = $this->getPaginationForIndex( $indexTitle );
352        $language = $indexTitle->getPageLanguage();
353
354        $pageInPagination = $pagination->getPageNumber( $pageTitle );
355        $dispNum = $pagination->getDisplayedPageNumber( $pageInPagination );
356
357        return [ [
358            'position' => $pageInPagination,
359            'display' => $dispNum->getFormattedPageNumber( $language ),
360            'raw' => $dispNum->getRawPageNumber( $language )
361        ] ];
362    }
363}