Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.75% covered (success)
98.75%
79 / 80
85.71% covered (warning)
85.71%
6 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConfigParser
98.75% covered (success)
98.75%
79 / 80
85.71% covered (warning)
85.71%
6 / 7
31
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getParsedConfig
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getTemplates
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 parseConfig
100.00% covered (success)
100.00%
46 / 46
100.00% covered (success)
100.00%
1 / 1
17
 parseArrayValues
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 parseValue
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 updateTemplates
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3namespace MediaWiki\Extension\MediaUploader\Config;
4
5use MediaWiki\Page\PageReference;
6use Parser;
7use ParserFactory;
8use ParserOptions;
9use ParserOutput;
10use Title;
11
12/**
13 * Class responsible for parsing wikitext in MediaUploader's config.
14 * This applies both to the main "global" config and campaigns.
15 */
16class ConfigParser {
17
18    /** @var ParserFactory */
19    private $parserFactory;
20
21    /** @var ParserOptions */
22    private $parserOptions;
23
24    /** @var Title */
25    private $pageRef;
26
27    /** @var array */
28    private $unparsedConfig;
29
30    /** @var array|null */
31    private $parsedConfig = null;
32
33    /** @var array */
34    private $templates = [];
35
36    /**
37     * @param PageReference $pageRef campaign page or title of Special:MediaUploader
38     *   if not parsing a campaign.
39     * @param ParserFactory $parserFactory
40     * @param ParserOptions $parserOptions
41     * @param array $configToParse
42     *
43     * @internal Only for use by ConfigParserFactory
44     */
45    public function __construct(
46        PageReference $pageRef,
47        ParserFactory $parserFactory,
48        ParserOptions $parserOptions,
49        array $configToParse
50    ) {
51        $options = clone $parserOptions;
52        // Open links in a new tab for slightly better UX
53        $options->setOption( 'externalLinkTarget', '_blank' );
54
55        $this->pageRef = $pageRef;
56        $this->parserFactory = $parserFactory;
57        $this->parserOptions = $options;
58        $this->unparsedConfig = $configToParse;
59    }
60
61    /**
62     * Returns the parsed config array.
63     *
64     * @return array
65     */
66    public function getParsedConfig(): array {
67        if ( $this->parsedConfig === null ) {
68            $this->parseConfig();
69        }
70        return $this->parsedConfig;
71    }
72
73    /**
74     * Returns the templates used in this config
75     *
76     * @return array [ ns => [ dbk => [ page_id, rev_id ] ] ]
77     */
78    public function getTemplates(): array {
79        if ( $this->parsedConfig === null ) {
80            $this->parseConfig();
81        }
82        return $this->templates;
83    }
84
85    /**
86     * Does the actual parsing work.
87     *
88     * @return void
89     */
90    private function parseConfig(): void {
91        $parsedConfig = [];
92        foreach ( $this->unparsedConfig as $key => $value ) {
93            switch ( $key ) {
94                case 'title':
95                case 'description':
96                    $parsedConfig[$key] = $this->parseValue( $value );
97                    break;
98                case 'display':
99                    foreach ( $value as $option => $optionValue ) {
100                        if ( is_array( $optionValue ) ) {
101                            $parsedConfig['display'][$option] = $this->parseArrayValues(
102                                $optionValue,
103                                [ 'label' ]
104                            );
105                        } else {
106                            $parsedConfig['display'][$option] = $this->parseValue( $optionValue );
107                        }
108                    }
109                    break;
110                case 'tutorial':
111                    if ( ( $value['enabled'] ?? true ) && ( $value['wikitext'] ?? false ) ) {
112                        // Parse tutorial wikitext
113                        $parsedConfig['tutorial'] = [
114                            'enabled' => true,
115                            'skip' => $value['skip'] ?? false,
116                            'html' => $this->parseValue( $value['wikitext'] ),
117                        ];
118                    } else {
119                        // The tutorial is not present or is disabled
120                        $parsedConfig['tutorial'] = [
121                            'enabled' => false,
122                            'skip' => true,
123                            'html' => '',
124                        ];
125                    }
126                    break;
127                case 'fields':
128                    $parsedConfig['fields'] = [];
129                    foreach ( $value as $fieldName => $field ) {
130                        $parsedConfig['fields'][$fieldName] = $this->parseArrayValues(
131                            $field,
132                            [ 'label', 'help', 'options' ]
133                        );
134                    }
135                    break;
136                case 'whileActive':
137                case 'afterActive':
138                case 'beforeActive':
139                    if ( array_key_exists( 'display', $value ) ) {
140                        $value['display'] = $this->parseArrayValues( $value['display'] );
141                    }
142                    $parsedConfig[$key] = $value;
143                    break;
144                default:
145                    $parsedConfig[$key] = $value;
146                    break;
147            }
148        }
149
150        $this->parsedConfig = $parsedConfig;
151    }
152
153    /**
154     * Parses the values in an associative array as wikitext
155     *
156     * @param array $array
157     * @param array|null $forKeys Array of keys whose values should be parsed
158     *
159     * @return array
160     */
161    private function parseArrayValues( array $array, $forKeys = null ): array {
162        $parsed = [];
163        foreach ( $array as $key => $value ) {
164            if ( $forKeys !== null ) {
165                if ( in_array( $key, $forKeys ) ) {
166                    if ( is_array( $value ) ) {
167                        $parsed[$key] = $this->parseArrayValues( $value );
168                    } else {
169                        $parsed[$key] = $this->parseValue( $value );
170                    }
171                } else {
172                    $parsed[$key] = $value;
173                }
174            } else {
175                $parsed[$key] = $this->parseValue( $value );
176            }
177        }
178        return $parsed;
179    }
180
181    /**
182     * Parses a wikitext fragment to HTML
183     *
184     * @param string $value Wikitext to parse
185     *
186     * @return string HTML
187     */
188    private function parseValue( string $value ): string {
189        $output = $this->parserFactory->create()->parse(
190            $value, $this->pageRef, $this->parserOptions
191        );
192        $parsed = $output->getText( [
193            'enableSectionEditLinks' => false,
194        ] );
195
196        $this->updateTemplates( $output );
197
198        return Parser::stripOuterParagraph( $parsed );
199    }
200
201    /**
202     * Update internal list of templates used in parsing this config
203     *
204     * @param ParserOutput $parserOutput
205     *
206     * @return void
207     */
208    private function updateTemplates( ParserOutput $parserOutput ): void {
209        $templateIds = $parserOutput->getTemplateIds();
210        foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
211            foreach ( $templates as $dbk => $id ) {
212                $this->templates[$ns][$dbk] = [ $id, $templateIds[$ns][$dbk] ];
213            }
214        }
215    }
216}