Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
93 / 93
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
EnumDef
100.00% covered (success)
100.00%
93 / 93
100.00% covered (success)
100.00%
8 / 8
29
100.00% covered (success)
100.00%
1 / 1
 validate
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
7
 checkSettings
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
9
 getEnumValues
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stringifyValue
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getParamInfo
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
3
 getHelpInfo
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
3
 sortEnumValues
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
3
 getEnumValuesForHelp
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace Wikimedia\ParamValidator\TypeDef;
4
5use Wikimedia\Message\DataMessageValue;
6use Wikimedia\Message\ListParam;
7use Wikimedia\Message\ListType;
8use Wikimedia\Message\MessageParam;
9use Wikimedia\Message\MessageValue;
10use Wikimedia\Message\ParamType;
11use Wikimedia\Message\ScalarParam;
12use Wikimedia\ParamValidator\ParamValidator;
13use Wikimedia\ParamValidator\TypeDef;
14
15/**
16 * Type definition for enumeration types.
17 *
18 * This class expects that PARAM_TYPE is an array of allowed values. Subclasses
19 * may override getEnumValues() to determine the allowed values differently.
20 *
21 * The result from validate() is one of the defined values.
22 *
23 * Failure codes:
24 *  - 'badvalue': The value is not a recognized value. No data.
25 *
26 * Additional codes may be generated when using certain PARAM constants. See
27 * the constants' documentation for details.
28 *
29 * @since 1.34
30 * @unstable
31 */
32class EnumDef extends TypeDef {
33
34    /**
35     * (array) Associative array of deprecated values.
36     *
37     * Keys are the deprecated parameter values. Value is one of the following:
38     *  - null: Parameter isn't actually deprecated.
39     *  - true: Parameter is deprecated.
40     *  - MessageValue: Parameter is deprecated, and this message (converted to a DataMessageValue)
41     *    is used in place of the default for passing to $this->failure().
42     *
43     * Note that this does not add any values to the enumeration, it only
44     * documents existing values as being deprecated.
45     *
46     * Failure codes: (non-fatal)
47     *  - 'deprecated-value': A deprecated value was encountered. No data.
48     */
49    public const PARAM_DEPRECATED_VALUES = 'param-deprecated-values';
50
51    public function validate( $name, $value, array $settings, array $options ) {
52        $values = $this->getEnumValues( $name, $settings, $options );
53
54        if ( in_array( $value, $values, true ) ) {
55            // Set a warning if a deprecated parameter value has been passed
56            if ( empty( $options['is-default'] ) &&
57                isset( $settings[self::PARAM_DEPRECATED_VALUES][$value] )
58            ) {
59                $msg = $settings[self::PARAM_DEPRECATED_VALUES][$value];
60                if ( $msg instanceof MessageValue ) {
61                    $message = DataMessageValue::new(
62                        $msg->getKey(),
63                        $msg->getParams(),
64                        'deprecated-value',
65                        $msg instanceof DataMessageValue ? $msg->getData() : null
66                    );
67                } else {
68                    $message = $this->failureMessage( 'deprecated-value' );
69                }
70                $this->failure( $message, $name, $value, $settings, $options, false );
71            }
72
73            return $value;
74        }
75
76        $isMulti = isset( $options['values-list'] );
77        $this->failure(
78            $this->failureMessage( 'badvalue', [], $isMulti ? 'enummulti' : 'enumnotmulti' )
79                ->textListParams( array_map( static function ( $v ) {
80                    return new ScalarParam( ParamType::PLAINTEXT, $v );
81                }, $values ) )
82                ->numParams( count( $values ) ),
83            $name, $value, $settings, $options
84        );
85    }
86
87    public function checkSettings( string $name, $settings, array $options, array $ret ): array {
88        $ret = parent::checkSettings( $name, $settings, $options, $ret );
89
90        $ret['allowedKeys'][] = self::PARAM_DEPRECATED_VALUES;
91
92        $dv = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
93        if ( !is_array( $dv ) ) {
94            $ret['issues'][self::PARAM_DEPRECATED_VALUES] = 'PARAM_DEPRECATED_VALUES must be an array, got '
95                . gettype( $dv );
96        } else {
97            $values = array_map( function ( $v ) use ( $name, $settings, $options ) {
98                return $this->stringifyValue( $name, $v, $settings, $options );
99            }, $this->getEnumValues( $name, $settings, $options ) );
100            foreach ( $dv as $k => $v ) {
101                $k = $this->stringifyValue( $name, $k, $settings, $options );
102                if ( !in_array( $k, $values, true ) ) {
103                    $ret['issues'][] = "PARAM_DEPRECATED_VALUES contains \"$k\", which is not "
104                        . 'one of the enumerated values';
105                } elseif ( $v instanceof MessageValue ) {
106                    $ret['messages'][] = $v;
107                } elseif ( $v !== null && $v !== true ) {
108                    $type = $v === false ? 'false' : ( is_object( $v ) ? get_class( $v ) : gettype( $v ) );
109                    $ret['issues'][] = 'Values in PARAM_DEPRECATED_VALUES must be null, true, or MessageValue, '
110                        . "but value for \"$k\" is $type";
111                }
112            }
113        }
114
115        return $ret;
116    }
117
118    public function getEnumValues( $name, array $settings, array $options ) {
119        return array_values( $settings[ParamValidator::PARAM_TYPE] );
120    }
121
122    public function stringifyValue( $name, $value, array $settings, array $options ) {
123        if ( !is_array( $value ) ) {
124            return parent::stringifyValue( $name, $value, $settings, $options );
125        }
126
127        return ParamValidator::implodeMultiValue( $value );
128    }
129
130    public function getParamInfo( $name, array $settings, array $options ) {
131        $info = parent::getParamInfo( $name, $settings, $options );
132
133        $info['type'] = $this->sortEnumValues(
134            $name,
135            $this->getEnumValues( $name, $settings, $options ),
136            $settings,
137            $options
138        );
139
140        if ( !empty( $settings[self::PARAM_DEPRECATED_VALUES] ) ) {
141            $deprecatedValues = array_intersect(
142                array_keys( $settings[self::PARAM_DEPRECATED_VALUES] ),
143                $this->getEnumValues( $name, $settings, $options )
144            );
145            if ( $deprecatedValues ) {
146                $deprecatedValues = $this->sortEnumValues( $name, $deprecatedValues, $settings, $options );
147                $info['deprecatedvalues'] = array_values( $deprecatedValues );
148            }
149        }
150
151        return $info;
152    }
153
154    public function getHelpInfo( $name, array $settings, array $options ) {
155        $info = parent::getHelpInfo( $name, $settings, $options );
156
157        $isMulti = !empty( $settings[ParamValidator::PARAM_ISMULTI] );
158
159        $values = $this->getEnumValuesForHelp( $name, $settings, $options );
160        $count = count( $values );
161
162        $i = array_search( '', $values, true );
163        if ( $i === false ) {
164            $valuesParam = new ListParam( ListType::COMMA, $values );
165        } else {
166            unset( $values[$i] );
167            $valuesParam = MessageValue::new( 'paramvalidator-help-type-enum-can-be-empty' )
168                ->commaListParams( $values )
169                ->numParams( count( $values ) );
170        }
171
172        $info[ParamValidator::PARAM_TYPE] = MessageValue::new( 'paramvalidator-help-type-enum' )
173            ->params( $isMulti ? 2 : 1 )
174            ->params( $valuesParam )
175            ->numParams( $count );
176
177        // Suppress standard ISMULTI message, it should be incorporated into our type message.
178        $info[ParamValidator::PARAM_ISMULTI] = null;
179
180        return $info;
181    }
182
183    /**
184     * Sort enum values for help/param info output
185     *
186     * @param string $name Parameter name being described.
187     * @param string[] $values Values being sorted
188     * @param array $settings Parameter settings array.
189     * @param array $options Options array.
190     * @return string[]
191     */
192    protected function sortEnumValues(
193        string $name, array $values, array $settings, array $options
194    ): array {
195        // sort values by deprecation status and name
196        $flags = [];
197        foreach ( $values as $k => $value ) {
198            $flag = 0;
199            if ( isset( $settings[self::PARAM_DEPRECATED_VALUES][$value] ) ) {
200                $flag |= 1;
201            }
202            $flags[$k] = $flag;
203        }
204        array_multisort( $flags, $values, SORT_NATURAL );
205
206        return $values;
207    }
208
209    /**
210     * Return enum values formatted for the help message
211     *
212     * @param string $name Parameter name being described.
213     * @param array $settings Parameter settings array.
214     * @param array $options Options array.
215     * @return (MessageParam|string)[]
216     */
217    protected function getEnumValuesForHelp( $name, array $settings, array $options ) {
218        $values = $this->getEnumValues( $name, $settings, $options );
219        $values = $this->sortEnumValues( $name, $values, $settings, $options );
220
221        // @todo Indicate deprecated values in some manner. Probably that needs
222        // MessageValue and/or MessageParam to have a generic ability to wrap
223        // values in HTML without that HTML coming out in the text format too.
224
225        return $values;
226    }
227
228}