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 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResourceLoaderData
0.00% covered (danger)
0.00%
0 / 89
0.00% covered (danger)
0.00%
0 / 7
462
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
 addOptionalDependencies
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 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 ExtensionRegistry;
13use MediaWiki\Config\Config;
14use MediaWiki\Extension\DiscussionTools\Hooks\HookRunner;
15use MediaWiki\MediaWikiServices;
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     * @param MessageLocalizer $context
64     * @param Config $config
65     * @return array[] Map from internal name to array of parameters for MessageLocalizer::msg()
66     * @phan-return non-empty-array[]
67     */
68    private static function getTermsOfUseMessages(
69        MessageLocalizer $context, Config $config
70    ): array {
71        $messages = [
72            'reply' => [ 'discussiontools-replywidget-terms-click',
73                $context->msg( 'discussiontools-replywidget-reply' )->text() ],
74            'newtopic' => [ 'discussiontools-replywidget-terms-click',
75                $context->msg( 'discussiontools-replywidget-newtopic' )->text() ],
76        ];
77
78        $hookRunner = new HookRunner( MediaWikiServices::getInstance()->getHookContainer() );
79        $hookRunner->onDiscussionToolsTermsOfUseMessages( $messages, $context, $config );
80
81        return $messages;
82    }
83
84    /**
85     * Return parsed terms-of-use messages, for use in a ResourceLoader module.
86     */
87    public static function getTermsOfUseMessagesParsed(
88        MessageLocalizer $context, Config $config
89    ): array {
90        $messages = static::getTermsOfUseMessages( $context, $config );
91        foreach ( $messages as &$msg ) {
92            $msg = $context->msg( ...$msg )->parse();
93        }
94        return $messages;
95    }
96
97    /**
98     * Return information about terms-of-use messages, for use in a ResourceLoader module as
99     * 'versionCallback'. This is to avoid calling the parser from version invalidation code.
100     */
101    public static function getTermsOfUseMessagesVersion(
102        MessageLocalizer $context, Config $config
103    ): array {
104        $messages = static::getTermsOfUseMessages( $context, $config );
105        foreach ( $messages as &$msg ) {
106            $message = $context->msg( ...$msg );
107            $msg = [
108                // Include the text of the message, in case the canonical translation changes
109                $message->plain(),
110                // Include the page touched time, in case the on-wiki override is invalidated
111                Title::makeTitle( NS_MEDIAWIKI, ucfirst( $message->getKey() ) )->getTouched(),
112            ];
113        }
114        return $messages;
115    }
116
117    /**
118     * Add optional dependencies to a ResourceLoader module definition depending on loaded extensions.
119     */
120    public static function addOptionalDependencies( array $info ): RL\Module {
121        $extensionRegistry = ExtensionRegistry::getInstance();
122
123        foreach ( $info['optionalDependencies'] as $ext => $deps ) {
124            if ( $extensionRegistry->isLoaded( $ext ) ) {
125                $info['dependencies'] = array_merge( $info['dependencies'], (array)$deps );
126            }
127        }
128
129        $class = $info['class'] ?? RL\FileModule::class;
130        return new $class( $info );
131    }
132
133    /**
134     * Generate the test module that includes all of the test data, based on the JSON files defining
135     * test cases.
136     */
137    public static function makeTestModule( array $info ): RL\Module {
138        // Some tests rely on PHP-only features or are too large for the Karma test runner.
139        // Skip them here. They are still tested in the PHP version.
140        $skipTests = [
141            'cases/modified.json' => [
142                // Too large, cause timeouts in Karma test runner
143                'enwiki oldparser',
144                'enwiki parsoid',
145                'enwiki oldparser (bullet indentation)',
146                'enwiki parsoid (bullet indentation)',
147                // These tests depend on #getTranscludedFrom(), which we didn't implement in JS
148                'arwiki no-paragraph parsoid',
149                'enwiki parsoid',
150                'Many comments consisting of a block template and a paragraph',
151                'Comment whose range almost exactly matches a template, but is not considered transcluded (T313100)',
152                'Accidental complex transclusion (T265528)',
153                'Accidental complex transclusion (T313093)',
154            ],
155        ];
156        $info['packageFiles'][] = [
157            'name' => 'skip.json',
158            'type' => 'data',
159            'content' => $skipTests,
160        ];
161
162        $keys = [ 'config', 'data', 'dom', 'expected' ];
163        foreach ( $info['testData'] as $path ) {
164            $info['packageFiles'][] = $path;
165            $localPath = $info['localBasePath'] . '/' . $path;
166            $data = json_decode( file_get_contents( $localPath ), true );
167            foreach ( $data as $case ) {
168                if ( isset( $case['name'] ) && in_array( $case['name'], $skipTests[$path] ?? [], true ) ) {
169                    continue;
170                }
171                foreach ( $case as $key => $val ) {
172                    if ( in_array( $key, $keys, true ) && is_string( $val ) ) {
173                        if ( str_ends_with( $val, '.json' ) ) {
174                            $info['packageFiles'][] = substr( $val, strlen( '../' ) );
175                        } elseif ( str_ends_with( $val, '.html' ) ) {
176                            $info['packageFiles'][] = [
177                                'name' => $val,
178                                'type' => 'data',
179                                'callback' => static function () use ( $info, $val ) {
180                                    $localPath = $info['localBasePath'] . '/' . $val;
181                                    return file_get_contents( $localPath );
182                                },
183                                'versionCallback' => static function () use ( $val ) {
184                                    return new RL\FilePath( $val );
185                                },
186                            ];
187                        }
188                    }
189                }
190            }
191        }
192        $class = $info['class'] ?? RL\FileModule::class;
193        return new $class( $info );
194    }
195}