Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResourceLoaderData
0.00% covered (danger)
0.00%
0 / 93
0.00% covered (danger)
0.00%
0 / 7
600
0.00% covered (danger)
0.00%
0 / 1
 getLocalData
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 getContentLanguageMessages
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
2
 getTermsOfUseMessages
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 getTermsOfUseMessagesParsed
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getTermsOfUseMessagesVersion
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
6
 addOptional
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
 makeTestModule
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 1
110
1<?php
2/**
3 * Utilities for ResourceLoader modules used by DiscussionTools.
4 *
5 * @file
6 * @ingroup Extensions
7 * @license MIT
8 */
9
10namespace MediaWiki\Extension\DiscussionTools;
11
12use MediaWiki\Config\Config;
13use MediaWiki\Extension\DiscussionTools\Hooks\HookRunner;
14use MediaWiki\MediaWikiServices;
15use MediaWiki\Registration\ExtensionRegistry;
16use MediaWiki\ResourceLoader as RL;
17use MediaWiki\Title\Title;
18use MessageLocalizer;
19
20class ResourceLoaderData {
21    /**
22     * Used for the 'ext.discussionTools.init' module and the test module.
23     *
24     * We need all of this data *in content language*. Some of it is already available in JS, but only
25     * in client language, so it's useless for us (e.g. digit transform table, month name messages).
26     */
27    public static function getLocalData(
28        RL\Context $context, Config $config, ?string $langCode = null
29    ): array {
30        $services = MediaWikiServices::getInstance();
31
32        if ( $langCode === null ) {
33            $langData = $services->getService( 'DiscussionTools.LanguageData' );
34        } else {
35            $langData = new LanguageData(
36                $services->getMainConfig(),
37                $services->getLanguageFactory()->getLanguage( $langCode ),
38                $services->getLanguageConverterFactory(),
39                $services->getSpecialPageFactory()
40            );
41        }
42
43        return $langData->getLocalData();
44    }
45
46    /**
47     * Return messages in content language, for use in a ResourceLoader module.
48     */
49    public static function getContentLanguageMessages(
50        RL\Context $context, Config $config, array $messagesKeys = []
51    ): array {
52        return array_combine(
53            $messagesKeys,
54            array_map( static function ( $key ) {
55                return wfMessage( $key )->inContentLanguage()->text();
56            }, $messagesKeys )
57        );
58    }
59
60    /**
61     * Return information about terms-of-use messages.
62     *
63     * @return array[] Map from internal name to array of parameters for MessageLocalizer::msg()
64     * @phan-return non-empty-array[]
65     */
66    private static function getTermsOfUseMessages(
67        MessageLocalizer $context, Config $config
68    ): array {
69        $messages = [
70            'reply' => [ 'discussiontools-replywidget-terms-click',
71                $context->msg( 'discussiontools-replywidget-reply' )->text() ],
72            'newtopic' => [ 'discussiontools-replywidget-terms-click',
73                $context->msg( 'discussiontools-replywidget-newtopic' )->text() ],
74        ];
75
76        $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
77        $hookRunner->onDiscussionToolsTermsOfUseMessages( $messages, $context, $config );
78
79        return $messages;
80    }
81
82    /**
83     * Return parsed terms-of-use messages, for use in a ResourceLoader module.
84     */
85    public static function getTermsOfUseMessagesParsed(
86        MessageLocalizer $context, Config $config
87    ): array {
88        $messages = static::getTermsOfUseMessages( $context, $config );
89        foreach ( $messages as &$msg ) {
90            $msg = $context->msg( ...$msg )->parse();
91        }
92        return $messages;
93    }
94
95    /**
96     * Return information about terms-of-use messages, for use in a ResourceLoader module as
97     * 'versionCallback'. This is to avoid calling the parser from version invalidation code.
98     */
99    public static function getTermsOfUseMessagesVersion(
100        MessageLocalizer $context, Config $config
101    ): array {
102        $messages = static::getTermsOfUseMessages( $context, $config );
103        foreach ( $messages as &$msg ) {
104            $message = $context->msg( ...$msg );
105            $msg = [
106                // Include the text of the message, in case the canonical translation changes
107                $message->plain(),
108                // Include the page touched time, in case the on-wiki override is invalidated
109                Title::makeTitle( NS_MEDIAWIKI, ucfirst( $message->getKey() ) )->getTouched(),
110            ];
111        }
112        return $messages;
113    }
114
115    /**
116     * Add optional array values to a ResourceLoader module definition depending on loaded extensions.
117     *
118     * "optional": {
119     *   "MyExtension": {
120     *     "dependencies": [ ... ],
121     *     "messages": [ ... ],
122     *     "packageFiles": [ ... ],
123     *     ...
124     *   }
125     * }
126     */
127    public static function addOptional( array $info ): RL\Module {
128        $extensionRegistry = ExtensionRegistry::getInstance();
129
130        if ( isset( $info['optional'] ) ) {
131            foreach ( $info['optional'] as $ext => $extOptions ) {
132                if ( $extensionRegistry->isLoaded( $ext ) ) {
133                    foreach ( $extOptions as $key => $values ) {
134                        if ( !isset( $info[$key] ) ) {
135                            $info[$key] = [];
136                        }
137                        // TODO: Support non-array properties
138                        $info[$key] = array_merge( $info[$key], (array)$values );
139                    }
140                }
141            }
142        }
143
144        $class = $info['class'] ?? RL\FileModule::class;
145        return new $class( $info );
146    }
147
148    /**
149     * Generate the test module that includes all of the test data, based on the JSON files defining
150     * test cases.
151     */
152    public static function makeTestModule( array $info ): RL\Module {
153        // Some tests rely on PHP-only features or are too large for the Karma test runner.
154        // Skip them here. They are still tested in the PHP version.
155        $skipTests = [
156            'cases/modified.json' => [
157                // Too large, cause timeouts in Karma test runner
158                'enwiki oldparser',
159                'enwiki parsoid',
160                'enwiki oldparser (bullet indentation)',
161                'enwiki parsoid (bullet indentation)',
162                // These tests depend on #getTranscludedFrom(), which we didn't implement in JS
163                'arwiki no-paragraph parsoid',
164                'enwiki parsoid',
165                'Many comments consisting of a block template and a paragraph',
166                'Comment whose range almost exactly matches a template, but is not considered transcluded (T313100)',
167                'Accidental complex transclusion (T265528)',
168                'Accidental complex transclusion (T313093)',
169            ],
170        ];
171        $info['packageFiles'][] = [
172            'name' => 'skip.json',
173            'type' => 'data',
174            'content' => $skipTests,
175        ];
176
177        $keys = [ 'config', 'data', 'dom', 'expected' ];
178        foreach ( $info['testData'] as $path ) {
179            $info['packageFiles'][] = $path;
180            $localPath = $info['localBasePath'] . '/' . $path;
181            $data = json_decode( file_get_contents( $localPath ), true );
182            foreach ( $data as $case ) {
183                if ( isset( $case['name'] ) && in_array( $case['name'], $skipTests[$path] ?? [], true ) ) {
184                    continue;
185                }
186                foreach ( $case as $key => $val ) {
187                    if ( in_array( $key, $keys, true ) && is_string( $val ) ) {
188                        if ( str_ends_with( $val, '.json' ) ) {
189                            $info['packageFiles'][] = substr( $val, strlen( '../' ) );
190                        } elseif ( str_ends_with( $val, '.html' ) ) {
191                            $info['packageFiles'][] = [
192                                'name' => $val,
193                                'type' => 'data',
194                                'callback' => static function () use ( $info, $val ) {
195                                    $localPath = $info['localBasePath'] . '/' . $val;
196                                    return file_get_contents( $localPath );
197                                },
198                                'versionCallback' => static function () use ( $val ) {
199                                    return new RL\FilePath( $val );
200                                },
201                            ];
202                        }
203                    }
204                }
205            }
206        }
207        $class = $info['class'] ?? RL\FileModule::class;
208        return new $class( $info );
209    }
210}