ApiParamInfo.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. <?php
  2. /**
  3. *
  4. *
  5. * Created on Dec 01, 2007
  6. *
  7. * Copyright © 2008 Roan Kattouw "<Firstname>.<Lastname>@gmail.com"
  8. *
  9. * This program is free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License along
  20. * with this program; if not, write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  22. * http://www.gnu.org/copyleft/gpl.html
  23. *
  24. * @file
  25. */
  26. /**
  27. * @ingroup API
  28. */
  29. class ApiParamInfo extends ApiBase {
  30. private $helpFormat;
  31. private $context;
  32. public function __construct( ApiMain $main, $action ) {
  33. parent::__construct( $main, $action );
  34. }
  35. public function execute() {
  36. // Get parameters
  37. $params = $this->extractRequestParams();
  38. $this->helpFormat = $params['helpformat'];
  39. $this->context = new RequestContext;
  40. $this->context->setUser( new User ); // anon to avoid caching issues
  41. $this->context->setLanguage( $this->getMain()->getLanguage() );
  42. if ( is_array( $params['modules'] ) ) {
  43. $modules = [];
  44. foreach ( $params['modules'] as $path ) {
  45. if ( $path === '*' || $path === '**' ) {
  46. $path = "main+$path";
  47. }
  48. if ( substr( $path, -2 ) === '+*' || substr( $path, -2 ) === ' *' ) {
  49. $submodules = true;
  50. $path = substr( $path, 0, -2 );
  51. $recursive = false;
  52. } elseif ( substr( $path, -3 ) === '+**' || substr( $path, -3 ) === ' **' ) {
  53. $submodules = true;
  54. $path = substr( $path, 0, -3 );
  55. $recursive = true;
  56. } else {
  57. $submodules = false;
  58. }
  59. if ( $submodules ) {
  60. try {
  61. $module = $this->getModuleFromPath( $path );
  62. } catch ( ApiUsageException $ex ) {
  63. foreach ( $ex->getStatusValue()->getErrors() as $error ) {
  64. $this->addWarning( $error );
  65. }
  66. continue;
  67. }
  68. $submodules = $this->listAllSubmodules( $module, $recursive );
  69. if ( $submodules ) {
  70. $modules = array_merge( $modules, $submodules );
  71. } else {
  72. $this->addWarning( [ 'apierror-badmodule-nosubmodules', $path ], 'badmodule' );
  73. }
  74. } else {
  75. $modules[] = $path;
  76. }
  77. }
  78. } else {
  79. $modules = [];
  80. }
  81. if ( is_array( $params['querymodules'] ) ) {
  82. $queryModules = $params['querymodules'];
  83. foreach ( $queryModules as $m ) {
  84. $modules[] = 'query+' . $m;
  85. }
  86. } else {
  87. $queryModules = [];
  88. }
  89. if ( is_array( $params['formatmodules'] ) ) {
  90. $formatModules = $params['formatmodules'];
  91. foreach ( $formatModules as $m ) {
  92. $modules[] = $m;
  93. }
  94. } else {
  95. $formatModules = [];
  96. }
  97. $modules = array_unique( $modules );
  98. $res = [];
  99. foreach ( $modules as $m ) {
  100. try {
  101. $module = $this->getModuleFromPath( $m );
  102. } catch ( ApiUsageException $ex ) {
  103. foreach ( $ex->getStatusValue()->getErrors() as $error ) {
  104. $this->addWarning( $error );
  105. }
  106. continue;
  107. }
  108. $key = 'modules';
  109. // Back compat
  110. $isBCQuery = false;
  111. if ( $module->getParent() && $module->getParent()->getModuleName() == 'query' &&
  112. in_array( $module->getModuleName(), $queryModules )
  113. ) {
  114. $isBCQuery = true;
  115. $key = 'querymodules';
  116. }
  117. if ( in_array( $module->getModuleName(), $formatModules ) ) {
  118. $key = 'formatmodules';
  119. }
  120. $item = $this->getModuleInfo( $module );
  121. if ( $isBCQuery ) {
  122. $item['querytype'] = $item['group'];
  123. }
  124. $res[$key][] = $item;
  125. }
  126. $result = $this->getResult();
  127. $result->addValue( [ $this->getModuleName() ], 'helpformat', $this->helpFormat );
  128. foreach ( $res as $key => $stuff ) {
  129. ApiResult::setIndexedTagName( $res[$key], 'module' );
  130. }
  131. if ( $params['mainmodule'] ) {
  132. $res['mainmodule'] = $this->getModuleInfo( $this->getMain() );
  133. }
  134. if ( $params['pagesetmodule'] ) {
  135. $pageSet = new ApiPageSet( $this->getMain()->getModuleManager()->getModule( 'query' ) );
  136. $res['pagesetmodule'] = $this->getModuleInfo( $pageSet );
  137. unset( $res['pagesetmodule']['name'] );
  138. unset( $res['pagesetmodule']['path'] );
  139. unset( $res['pagesetmodule']['group'] );
  140. }
  141. $result->addValue( null, $this->getModuleName(), $res );
  142. }
  143. /**
  144. * List all submodules of a module
  145. * @param ApiBase $module
  146. * @param bool $recursive
  147. * @return string[]
  148. */
  149. private function listAllSubmodules( ApiBase $module, $recursive ) {
  150. $manager = $module->getModuleManager();
  151. if ( $manager ) {
  152. $paths = [];
  153. $names = $manager->getNames();
  154. sort( $names );
  155. foreach ( $names as $name ) {
  156. $submodule = $manager->getModule( $name );
  157. $paths[] = $submodule->getModulePath();
  158. if ( $recursive && $submodule->getModuleManager() ) {
  159. $paths = array_merge( $paths, $this->listAllSubmodules( $submodule, $recursive ) );
  160. }
  161. }
  162. }
  163. return $paths;
  164. }
  165. /**
  166. * @param array &$res Result array
  167. * @param string $key Result key
  168. * @param Message[] $msgs
  169. * @param bool $joinLists
  170. */
  171. protected function formatHelpMessages( array &$res, $key, array $msgs, $joinLists = false ) {
  172. switch ( $this->helpFormat ) {
  173. case 'none':
  174. break;
  175. case 'wikitext':
  176. $ret = [];
  177. foreach ( $msgs as $m ) {
  178. $ret[] = $m->setContext( $this->context )->text();
  179. }
  180. $res[$key] = implode( "\n\n", $ret );
  181. if ( $joinLists ) {
  182. $res[$key] = preg_replace( '!^(([*#:;])[^\n]*)\n\n(?=\2)!m', "$1\n", $res[$key] );
  183. }
  184. break;
  185. case 'html':
  186. $ret = [];
  187. foreach ( $msgs as $m ) {
  188. $ret[] = $m->setContext( $this->context )->parseAsBlock();
  189. }
  190. $ret = implode( "\n", $ret );
  191. if ( $joinLists ) {
  192. $ret = preg_replace( '!\s*</([oud]l)>\s*<\1>\s*!', "\n", $ret );
  193. }
  194. $res[$key] = Parser::stripOuterParagraph( $ret );
  195. break;
  196. case 'raw':
  197. $res[$key] = [];
  198. foreach ( $msgs as $m ) {
  199. $a = [
  200. 'key' => $m->getKey(),
  201. 'params' => $m->getParams(),
  202. ];
  203. ApiResult::setIndexedTagName( $a['params'], 'param' );
  204. if ( $m instanceof ApiHelpParamValueMessage ) {
  205. $a['forvalue'] = $m->getParamValue();
  206. }
  207. $res[$key][] = $a;
  208. }
  209. ApiResult::setIndexedTagName( $res[$key], 'msg' );
  210. break;
  211. }
  212. }
  213. /**
  214. * @param ApiBase $module
  215. * @return array
  216. */
  217. private function getModuleInfo( $module ) {
  218. $ret = [];
  219. $path = $module->getModulePath();
  220. $ret['name'] = $module->getModuleName();
  221. $ret['classname'] = get_class( $module );
  222. $ret['path'] = $path;
  223. if ( !$module->isMain() ) {
  224. $ret['group'] = $module->getParent()->getModuleManager()->getModuleGroup(
  225. $module->getModuleName()
  226. );
  227. }
  228. $ret['prefix'] = $module->getModulePrefix();
  229. $sourceInfo = $module->getModuleSourceInfo();
  230. if ( $sourceInfo ) {
  231. $ret['source'] = $sourceInfo['name'];
  232. if ( isset( $sourceInfo['namemsg'] ) ) {
  233. $ret['sourcename'] = $this->context->msg( $sourceInfo['namemsg'] )->text();
  234. } else {
  235. $ret['sourcename'] = $ret['source'];
  236. }
  237. $link = SpecialPage::getTitleFor( 'Version', 'License/' . $sourceInfo['name'] )->getFullURL();
  238. if ( isset( $sourceInfo['license-name'] ) ) {
  239. $ret['licensetag'] = $sourceInfo['license-name'];
  240. $ret['licenselink'] = (string)$link;
  241. } elseif ( SpecialVersion::getExtLicenseFileName( dirname( $sourceInfo['path'] ) ) ) {
  242. $ret['licenselink'] = (string)$link;
  243. }
  244. }
  245. $this->formatHelpMessages( $ret, 'description', $module->getFinalDescription() );
  246. foreach ( $module->getHelpFlags() as $flag ) {
  247. $ret[$flag] = true;
  248. }
  249. $ret['helpurls'] = (array)$module->getHelpUrls();
  250. if ( isset( $ret['helpurls'][0] ) && $ret['helpurls'][0] === false ) {
  251. $ret['helpurls'] = [];
  252. }
  253. ApiResult::setIndexedTagName( $ret['helpurls'], 'helpurl' );
  254. if ( $this->helpFormat !== 'none' ) {
  255. $ret['examples'] = [];
  256. $examples = $module->getExamplesMessages();
  257. foreach ( $examples as $qs => $msg ) {
  258. $item = [
  259. 'query' => $qs
  260. ];
  261. $msg = ApiBase::makeMessage( $msg, $this->context, [
  262. $module->getModulePrefix(),
  263. $module->getModuleName(),
  264. $module->getModulePath()
  265. ] );
  266. $this->formatHelpMessages( $item, 'description', [ $msg ] );
  267. if ( isset( $item['description'] ) ) {
  268. if ( is_array( $item['description'] ) ) {
  269. $item['description'] = $item['description'][0];
  270. } else {
  271. ApiResult::setSubelementsList( $item, 'description' );
  272. }
  273. }
  274. $ret['examples'][] = $item;
  275. }
  276. ApiResult::setIndexedTagName( $ret['examples'], 'example' );
  277. }
  278. $ret['parameters'] = [];
  279. $params = $module->getFinalParams( ApiBase::GET_VALUES_FOR_HELP );
  280. $paramDesc = $module->getFinalParamDescription();
  281. foreach ( $params as $name => $settings ) {
  282. if ( !is_array( $settings ) ) {
  283. $settings = [ ApiBase::PARAM_DFLT => $settings ];
  284. }
  285. $item = [
  286. 'name' => $name
  287. ];
  288. if ( isset( $paramDesc[$name] ) ) {
  289. $this->formatHelpMessages( $item, 'description', $paramDesc[$name], true );
  290. }
  291. $item['required'] = !empty( $settings[ApiBase::PARAM_REQUIRED] );
  292. if ( !empty( $settings[ApiBase::PARAM_DEPRECATED] ) ) {
  293. $item['deprecated'] = true;
  294. }
  295. if ( $name === 'token' && $module->needsToken() ) {
  296. $item['tokentype'] = $module->needsToken();
  297. }
  298. if ( !isset( $settings[ApiBase::PARAM_TYPE] ) ) {
  299. $dflt = isset( $settings[ApiBase::PARAM_DFLT] )
  300. ? $settings[ApiBase::PARAM_DFLT]
  301. : null;
  302. if ( is_bool( $dflt ) ) {
  303. $settings[ApiBase::PARAM_TYPE] = 'boolean';
  304. } elseif ( is_string( $dflt ) || is_null( $dflt ) ) {
  305. $settings[ApiBase::PARAM_TYPE] = 'string';
  306. } elseif ( is_int( $dflt ) ) {
  307. $settings[ApiBase::PARAM_TYPE] = 'integer';
  308. }
  309. }
  310. if ( isset( $settings[ApiBase::PARAM_DFLT] ) ) {
  311. switch ( $settings[ApiBase::PARAM_TYPE] ) {
  312. case 'boolean':
  313. $item['default'] = (bool)$settings[ApiBase::PARAM_DFLT];
  314. break;
  315. case 'string':
  316. case 'text':
  317. case 'password':
  318. $item['default'] = strval( $settings[ApiBase::PARAM_DFLT] );
  319. break;
  320. case 'integer':
  321. case 'limit':
  322. $item['default'] = intval( $settings[ApiBase::PARAM_DFLT] );
  323. break;
  324. case 'timestamp':
  325. $item['default'] = wfTimestamp( TS_ISO_8601, $settings[ApiBase::PARAM_DFLT] );
  326. break;
  327. default:
  328. $item['default'] = $settings[ApiBase::PARAM_DFLT];
  329. break;
  330. }
  331. }
  332. $item['multi'] = !empty( $settings[ApiBase::PARAM_ISMULTI] );
  333. if ( $item['multi'] ) {
  334. $item['lowlimit'] = !empty( $settings[ApiBase::PARAM_ISMULTI_LIMIT1] )
  335. ? $settings[ApiBase::PARAM_ISMULTI_LIMIT1]
  336. : ApiBase::LIMIT_SML1;
  337. $item['highlimit'] = !empty( $settings[ApiBase::PARAM_ISMULTI_LIMIT2] )
  338. ? $settings[ApiBase::PARAM_ISMULTI_LIMIT2]
  339. : ApiBase::LIMIT_SML2;
  340. $item['limit'] = $this->getMain()->canApiHighLimits()
  341. ? $item['highlimit']
  342. : $item['lowlimit'];
  343. }
  344. if ( !empty( $settings[ApiBase::PARAM_ALLOW_DUPLICATES] ) ) {
  345. $item['allowsduplicates'] = true;
  346. }
  347. if ( isset( $settings[ApiBase::PARAM_TYPE] ) ) {
  348. if ( $settings[ApiBase::PARAM_TYPE] === 'submodule' ) {
  349. if ( isset( $settings[ApiBase::PARAM_SUBMODULE_MAP] ) ) {
  350. ksort( $settings[ApiBase::PARAM_SUBMODULE_MAP] );
  351. $item['type'] = array_keys( $settings[ApiBase::PARAM_SUBMODULE_MAP] );
  352. $item['submodules'] = $settings[ApiBase::PARAM_SUBMODULE_MAP];
  353. } else {
  354. $item['type'] = $module->getModuleManager()->getNames( $name );
  355. sort( $item['type'] );
  356. $prefix = $module->isMain()
  357. ? '' : ( $module->getModulePath() . '+' );
  358. $item['submodules'] = [];
  359. foreach ( $item['type'] as $v ) {
  360. $item['submodules'][$v] = $prefix . $v;
  361. }
  362. }
  363. if ( isset( $settings[ApiBase::PARAM_SUBMODULE_PARAM_PREFIX] ) ) {
  364. $item['submoduleparamprefix'] = $settings[ApiBase::PARAM_SUBMODULE_PARAM_PREFIX];
  365. }
  366. $deprecatedSubmodules = [];
  367. foreach ( $item['submodules'] as $v => $submodulePath ) {
  368. try {
  369. $submod = $this->getModuleFromPath( $submodulePath );
  370. if ( $submod && $submod->isDeprecated() ) {
  371. $deprecatedSubmodules[] = $v;
  372. }
  373. } catch ( ApiUsageException $ex ) {
  374. // Ignore
  375. }
  376. }
  377. if ( $deprecatedSubmodules ) {
  378. $item['type'] = array_merge(
  379. array_diff( $item['type'], $deprecatedSubmodules ),
  380. $deprecatedSubmodules
  381. );
  382. $item['deprecatedvalues'] = $deprecatedSubmodules;
  383. }
  384. } elseif ( $settings[ApiBase::PARAM_TYPE] === 'tags' ) {
  385. $item['type'] = ChangeTags::listExplicitlyDefinedTags();
  386. } else {
  387. $item['type'] = $settings[ApiBase::PARAM_TYPE];
  388. }
  389. if ( is_array( $item['type'] ) ) {
  390. // To prevent sparse arrays from being serialized to JSON as objects
  391. $item['type'] = array_values( $item['type'] );
  392. ApiResult::setIndexedTagName( $item['type'], 't' );
  393. }
  394. // Add 'allspecifier' if applicable
  395. if ( $item['type'] === 'namespace' ) {
  396. $allowAll = true;
  397. $allSpecifier = ApiBase::ALL_DEFAULT_STRING;
  398. } else {
  399. $allowAll = isset( $settings[ApiBase::PARAM_ALL] )
  400. ? $settings[ApiBase::PARAM_ALL]
  401. : false;
  402. $allSpecifier = ( is_string( $allowAll ) ? $allowAll : ApiBase::ALL_DEFAULT_STRING );
  403. }
  404. if ( $allowAll && $item['multi'] &&
  405. ( is_array( $item['type'] ) || $item['type'] === 'namespace' ) ) {
  406. $item['allspecifier'] = $allSpecifier;
  407. }
  408. if ( $item['type'] === 'namespace' &&
  409. isset( $settings[ApiBase::PARAM_EXTRA_NAMESPACES] ) &&
  410. is_array( $settings[ApiBase::PARAM_EXTRA_NAMESPACES] )
  411. ) {
  412. $item['extranamespaces'] = $settings[ApiBase::PARAM_EXTRA_NAMESPACES];
  413. ApiResult::setArrayType( $item['extranamespaces'], 'array' );
  414. ApiResult::setIndexedTagName( $item['extranamespaces'], 'ns' );
  415. }
  416. }
  417. if ( isset( $settings[ApiBase::PARAM_MAX] ) ) {
  418. $item['max'] = $settings[ApiBase::PARAM_MAX];
  419. }
  420. if ( isset( $settings[ApiBase::PARAM_MAX2] ) ) {
  421. $item['highmax'] = $settings[ApiBase::PARAM_MAX2];
  422. }
  423. if ( isset( $settings[ApiBase::PARAM_MIN] ) ) {
  424. $item['min'] = $settings[ApiBase::PARAM_MIN];
  425. }
  426. if ( !empty( $settings[ApiBase::PARAM_RANGE_ENFORCE] ) ) {
  427. $item['enforcerange'] = true;
  428. }
  429. if ( !empty( $settings[ApiBase::PARAM_DEPRECATED_VALUES] ) ) {
  430. $deprecatedValues = array_keys( $settings[ApiBase::PARAM_DEPRECATED_VALUES] );
  431. if ( is_array( $item['type'] ) ) {
  432. $deprecatedValues = array_intersect( $deprecatedValues, $item['type'] );
  433. }
  434. if ( $deprecatedValues ) {
  435. $item['deprecatedvalues'] = array_values( $deprecatedValues );
  436. ApiResult::setIndexedTagName( $item['deprecatedvalues'], 'v' );
  437. }
  438. }
  439. if ( !empty( $settings[ApiBase::PARAM_HELP_MSG_INFO] ) ) {
  440. $item['info'] = [];
  441. foreach ( $settings[ApiBase::PARAM_HELP_MSG_INFO] as $i ) {
  442. $tag = array_shift( $i );
  443. $info = [
  444. 'name' => $tag,
  445. ];
  446. if ( count( $i ) ) {
  447. $info['values'] = $i;
  448. ApiResult::setIndexedTagName( $info['values'], 'v' );
  449. }
  450. $this->formatHelpMessages( $info, 'text', [
  451. $this->context->msg( "apihelp-{$path}-paraminfo-{$tag}" )
  452. ->numParams( count( $i ) )
  453. ->params( $this->context->getLanguage()->commaList( $i ) )
  454. ->params( $module->getModulePrefix() )
  455. ] );
  456. ApiResult::setSubelementsList( $info, 'text' );
  457. $item['info'][] = $info;
  458. }
  459. ApiResult::setIndexedTagName( $item['info'], 'i' );
  460. }
  461. $ret['parameters'][] = $item;
  462. }
  463. ApiResult::setIndexedTagName( $ret['parameters'], 'param' );
  464. $dynamicParams = $module->dynamicParameterDocumentation();
  465. if ( $dynamicParams !== null ) {
  466. if ( $this->helpFormat === 'none' ) {
  467. $ret['dynamicparameters'] = true;
  468. } else {
  469. $dynamicParams = ApiBase::makeMessage( $dynamicParams, $this->context, [
  470. $module->getModulePrefix(),
  471. $module->getModuleName(),
  472. $module->getModulePath()
  473. ] );
  474. $this->formatHelpMessages( $ret, 'dynamicparameters', [ $dynamicParams ] );
  475. }
  476. }
  477. return $ret;
  478. }
  479. public function isReadMode() {
  480. return false;
  481. }
  482. public function getAllowedParams() {
  483. // back compat
  484. $querymodules = $this->getMain()->getModuleManager()
  485. ->getModule( 'query' )->getModuleManager()->getNames();
  486. sort( $querymodules );
  487. $formatmodules = $this->getMain()->getModuleManager()->getNames( 'format' );
  488. sort( $formatmodules );
  489. return [
  490. 'modules' => [
  491. ApiBase::PARAM_ISMULTI => true,
  492. ],
  493. 'helpformat' => [
  494. ApiBase::PARAM_DFLT => 'none',
  495. ApiBase::PARAM_TYPE => [ 'html', 'wikitext', 'raw', 'none' ],
  496. ],
  497. 'querymodules' => [
  498. ApiBase::PARAM_DEPRECATED => true,
  499. ApiBase::PARAM_ISMULTI => true,
  500. ApiBase::PARAM_TYPE => $querymodules,
  501. ],
  502. 'mainmodule' => [
  503. ApiBase::PARAM_DEPRECATED => true,
  504. ],
  505. 'pagesetmodule' => [
  506. ApiBase::PARAM_DEPRECATED => true,
  507. ],
  508. 'formatmodules' => [
  509. ApiBase::PARAM_DEPRECATED => true,
  510. ApiBase::PARAM_ISMULTI => true,
  511. ApiBase::PARAM_TYPE => $formatmodules,
  512. ]
  513. ];
  514. }
  515. protected function getExamplesMessages() {
  516. return [
  517. 'action=paraminfo&modules=parse|phpfm|query%2Ballpages|query%2Bsiteinfo'
  518. => 'apihelp-paraminfo-example-1',
  519. 'action=paraminfo&modules=query%2B*'
  520. => 'apihelp-paraminfo-example-2',
  521. ];
  522. }
  523. public function getHelpUrls() {
  524. return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Parameter_information';
  525. }
  526. }