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