Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
108 / 108
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
SubmoduleDef
100.00% covered (success)
100.00%
108 / 108
100.00% covered (success)
100.00%
6 / 6
42
100.00% covered (success)
100.00%
1 / 1
 checkSettings
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
8
 getEnumValues
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 getParamInfo
100.00% covered (success)
100.00%
35 / 35
100.00% covered (success)
100.00%
1 / 1
13
 getSubmoduleMap
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 sortEnumValues
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
8
 getEnumValuesForHelp
100.00% covered (success)
100.00%
23 / 23
100.00% covered (success)
100.00%
1 / 1
7
1<?php
2
3namespace MediaWiki\Api\Validator;
4
5use MediaWiki\Api\ApiBase;
6use MediaWiki\Api\ApiUsageException;
7use MediaWiki\Html\Html;
8use Wikimedia\ParamValidator\TypeDef\EnumDef;
9
10/**
11 * Type definition for submodule types
12 *
13 * A submodule type is an enum type for selecting Action API submodules.
14 *
15 * @since 1.35
16 */
17class SubmoduleDef extends EnumDef {
18
19    /**
20     * (string[]) Map parameter values to submodule paths.
21     *
22     * Default is to use all modules in $options['module']->getModuleManager()
23     * in the group matching the parameter name.
24     */
25    public const PARAM_SUBMODULE_MAP = 'param-submodule-map';
26
27    /**
28     * (string) Used to indicate the 'g' prefix added by ApiQueryGeneratorBase
29     * (and similar if anything else ever does that).
30     */
31    public const PARAM_SUBMODULE_PARAM_PREFIX = 'param-submodule-param-prefix';
32
33    public function checkSettings( string $name, $settings, array $options, array $ret ): array {
34        $map = $settings[self::PARAM_SUBMODULE_MAP] ?? [];
35        if ( !is_array( $map ) ) {
36            $ret['issues'][self::PARAM_SUBMODULE_MAP] = 'PARAM_SUBMODULE_MAP must be an array, got '
37                . gettype( $map );
38            // Prevent errors in parent::checkSettings()
39            $settings[self::PARAM_SUBMODULE_MAP] = null;
40        }
41
42        $ret = parent::checkSettings( $name, $settings, $options, $ret );
43
44        $ret['allowedKeys'] = array_merge( $ret['allowedKeys'], [
45            self::PARAM_SUBMODULE_MAP, self::PARAM_SUBMODULE_PARAM_PREFIX,
46        ] );
47
48        if ( is_array( $map ) ) {
49            $module = $options['module'];
50            foreach ( $map as $k => $v ) {
51                if ( !is_string( $v ) ) {
52                    $ret['issues'][] = 'Values for PARAM_SUBMODULE_MAP must be strings, '
53                        . "but value for \"$k\" is " . gettype( $v );
54                    continue;
55                }
56
57                try {
58                    $submod = $module->getModuleFromPath( $v );
59                } catch ( ApiUsageException $ex ) {
60                    $submod = null;
61                }
62                if ( !$submod ) {
63                    $ret['issues'][] = "PARAM_SUBMODULE_MAP contains \"$v\", which is not a valid module path";
64                }
65            }
66        }
67
68        if ( !is_string( $settings[self::PARAM_SUBMODULE_PARAM_PREFIX] ?? '' ) ) {
69            $ret['issues'][self::PARAM_SUBMODULE_PARAM_PREFIX] = 'PARAM_SUBMODULE_PARAM_PREFIX must be '
70                . 'a string, got ' . gettype( $settings[self::PARAM_SUBMODULE_PARAM_PREFIX] );
71        }
72
73        return $ret;
74    }
75
76    public function getEnumValues( $name, array $settings, array $options ) {
77        if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
78            $modules = array_keys( $settings[self::PARAM_SUBMODULE_MAP] );
79        } else {
80            $modules = $options['module']->getModuleManager()->getNames( $name );
81        }
82
83        return $modules;
84    }
85
86    public function getParamInfo( $name, array $settings, array $options ) {
87        $info = parent::getParamInfo( $name, $settings, $options );
88        /** @var ApiBase $module */
89        $module = $options['module'];
90
91        if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
92            $info['type'] = array_keys( $settings[self::PARAM_SUBMODULE_MAP] );
93            $info['submodules'] = $settings[self::PARAM_SUBMODULE_MAP];
94        } else {
95            $info['type'] = $module->getModuleManager()->getNames( $name );
96            $prefix = $module->isMain() ? '' : ( $module->getModulePath() . '+' );
97            $info['submodules'] = [];
98            foreach ( $info['type'] as $v ) {
99                $info['submodules'][$v] = $prefix . $v;
100            }
101        }
102        if ( isset( $settings[self::PARAM_SUBMODULE_PARAM_PREFIX] ) ) {
103            $info['submoduleparamprefix'] = $settings[self::PARAM_SUBMODULE_PARAM_PREFIX];
104        }
105
106        $submoduleFlags = []; // for sorting: higher flags are sorted later
107        $submoduleNames = []; // for sorting: lexicographical, ascending
108        foreach ( $info['submodules'] as $v => $submodulePath ) {
109            try {
110                $submod = $module->getModuleFromPath( $submodulePath );
111            } catch ( ApiUsageException $ex ) {
112                $submoduleFlags[] = 0;
113                $submoduleNames[] = $v;
114                continue;
115            }
116            $flags = 0;
117            if ( $submod && $submod->isDeprecated() ) {
118                $info['deprecatedvalues'][] = $v;
119                $flags |= 1;
120            }
121            if ( $submod && $submod->isInternal() ) {
122                $info['internalvalues'][] = $v;
123                $flags |= 2;
124            }
125            $submoduleFlags[] = $flags;
126            $submoduleNames[] = $v;
127        }
128        // sort $info['submodules'] and $info['type'] by $submoduleFlags and $submoduleNames
129        array_multisort( $submoduleFlags, $submoduleNames, $info['submodules'], $info['type'] );
130        if ( isset( $info['deprecatedvalues'] ) ) {
131            sort( $info['deprecatedvalues'] );
132        }
133        if ( isset( $info['internalvalues'] ) ) {
134            sort( $info['internalvalues'] );
135        }
136
137        return $info;
138    }
139
140    private function getSubmoduleMap( ApiBase $module, string $name, array $settings ): array {
141        if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
142            $map = $settings[self::PARAM_SUBMODULE_MAP];
143        } else {
144            $prefix = $module->isMain() ? '' : ( $module->getModulePath() . '+' );
145            $map = [];
146            foreach ( $module->getModuleManager()->getNames( $name ) as $submoduleName ) {
147                $map[$submoduleName] = $prefix . $submoduleName;
148            }
149        }
150
151        return $map;
152    }
153
154    protected function sortEnumValues(
155        string $name, array $values, array $settings, array $options
156    ): array {
157        $module = $options['module'];
158        $map = $this->getSubmoduleMap( $module, $name, $settings );
159
160        $submoduleFlags = []; // for sorting: higher flags are sorted later
161        foreach ( $values as $k => $v ) {
162            $flags = 0;
163            try {
164                $submod = isset( $map[$v] ) ? $module->getModuleFromPath( $map[$v] ) : null;
165                if ( $submod && $submod->isDeprecated() ) {
166                    $flags |= 1;
167                }
168                if ( $submod && $submod->isInternal() ) {
169                    $flags |= 2;
170                }
171            } catch ( ApiUsageException $ex ) {
172                // Ignore
173            }
174            $submoduleFlags[$k] = $flags;
175        }
176        array_multisort( $submoduleFlags, $values, SORT_NATURAL );
177
178        return $values;
179    }
180
181    protected function getEnumValuesForHelp( $name, array $settings, array $options ) {
182        $module = $options['module'];
183        $map = $this->getSubmoduleMap( $module, $name, $settings );
184        $defaultAttrs = [ 'dir' => 'ltr', 'lang' => 'en' ];
185
186        $values = [];
187        $submoduleFlags = []; // for sorting: higher flags are sorted later
188        $submoduleNames = []; // for sorting: lexicographical, ascending
189        foreach ( $map as $v => $m ) {
190            $attrs = $defaultAttrs;
191            $flags = 0;
192            try {
193                $submod = $module->getModuleFromPath( $m );
194                if ( $submod && $submod->isDeprecated() ) {
195                    $attrs['class'][] = 'apihelp-deprecated-value';
196                    $flags |= 1;
197                }
198                if ( $submod && $submod->isInternal() ) {
199                    $attrs['class'][] = 'apihelp-internal-value';
200                    $flags |= 2;
201                }
202            } catch ( ApiUsageException $ex ) {
203                // Ignore
204            }
205            $v = Html::element( 'span', $attrs, $v );
206            $values[] = "[[Special:ApiHelp/{$m}|{$v}]]";
207            $submoduleFlags[] = $flags;
208            $submoduleNames[] = $v;
209        }
210        // sort $values by $submoduleFlags and $submoduleNames
211        array_multisort( $submoduleFlags, $submoduleNames, SORT_NATURAL, $values, SORT_NATURAL );
212
213        return $values;
214    }
215
216}