1<?php 2 3namespace MediaWiki\Api\Validator; 4 5use ApiBase; 6use ApiUsageException; 7use 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} 217