MediaWiki  master
ApiBase.php
Go to the documentation of this file.
1 <?php
28 use Wikimedia\IPUtils;
30 
43 abstract class ApiBase extends ContextSource {
44 
46 
56  public const PARAM_DFLT = 0;
57 
59  public const PARAM_ISMULTI = 1;
60 
95  public const PARAM_TYPE = 2;
96 
98  public const PARAM_MAX = 3;
99 
104  public const PARAM_MAX2 = 4;
105 
107  public const PARAM_MIN = 5;
108 
110  public const PARAM_ALLOW_DUPLICATES = 6;
111 
113  public const PARAM_DEPRECATED = 7;
114 
119  public const PARAM_REQUIRED = 8;
120 
125  public const PARAM_RANGE_ENFORCE = 9;
126 
132  public const PARAM_HELP_MSG = 10;
133 
139  public const PARAM_HELP_MSG_APPEND = 11;
140 
149  public const PARAM_HELP_MSG_INFO = 12;
150 
156  public const PARAM_VALUE_LINKS = 13;
157 
165  public const PARAM_HELP_MSG_PER_VALUE = 14;
166 
173  public const PARAM_SUBMODULE_MAP = 15;
174 
180  public const PARAM_SUBMODULE_PARAM_PREFIX = 16;
181 
188  public const PARAM_ALL = 17;
189 
194  public const PARAM_EXTRA_NAMESPACES = 18;
195 
201  public const PARAM_SENSITIVE = 19;
202 
210  public const PARAM_DEPRECATED_VALUES = 20;
211 
216  public const PARAM_ISMULTI_LIMIT1 = 21;
217 
223  public const PARAM_ISMULTI_LIMIT2 = 22;
224 
229  public const PARAM_MAX_BYTES = 23;
230 
235  public const PARAM_MAX_CHARS = 24;
236 
253  public const PARAM_TEMPLATE_VARS = 25;
254 
257  public const ALL_DEFAULT_STRING = '*';
258 
260  public const LIMIT_BIG1 = 500;
262  public const LIMIT_BIG2 = 5000;
264  public const LIMIT_SML1 = 50;
266  public const LIMIT_SML2 = 500;
267 
273  public const GET_VALUES_FOR_HELP = 1;
274 
276  private static $extensionInfo = null;
277 
279  private static $filterIDsCache = [];
280 
282  private static $blockMsgMap = [
283  'blockedtext' => [ 'apierror-blocked', 'blocked' ],
284  'blockedtext-partial' => [ 'apierror-blocked-partial', 'blocked' ],
285  'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
286  'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
287  'blockedtext-composite' => [ 'apierror-blocked', 'blocked' ],
288  ];
289 
291  private $mMainModule;
294  private $mReplicaDB = null;
298  private $mParamCache = [];
300  private $mModuleSource = false;
301 
307  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
308  $this->mMainModule = $mainModule;
309  $this->mModuleName = $moduleName;
310  $this->mModulePrefix = $modulePrefix;
311 
312  if ( !$this->isMain() ) {
313  $this->setContext( $mainModule->getContext() );
314  }
315  }
316 
317  /************************************************************************/
338  abstract public function execute();
339 
345  public function getModuleManager() {
346  return null;
347  }
348 
358  public function getCustomPrinter() {
359  return null;
360  }
361 
373  protected function getExamplesMessages() {
374  return [];
375  }
376 
382  public function getHelpUrls() {
383  return [];
384  }
385 
398  protected function getAllowedParams( /* $flags = 0 */ ) {
399  // int $flags is not declared because it causes "Strict standards"
400  // warning. Most derived classes do not implement it.
401  return [];
402  }
403 
408  public function shouldCheckMaxlag() {
409  return true;
410  }
411 
416  public function isReadMode() {
417  return true;
418  }
419 
431  public function isWriteMode() {
432  return false;
433  }
434 
439  public function mustBePosted() {
440  return $this->needsToken() !== false;
441  }
442 
448  public function isDeprecated() {
449  return false;
450  }
451 
458  public function isInternal() {
459  return false;
460  }
461 
480  public function needsToken() {
481  return false;
482  }
483 
493  protected function getWebUITokenSalt( array $params ) {
494  return null;
495  }
496 
509  public function getConditionalRequestData( $condition ) {
510  return null;
511  }
512 
515  /************************************************************************/
524  public function getModuleName() {
525  return $this->mModuleName;
526  }
527 
532  public function getModulePrefix() {
533  return $this->mModulePrefix;
534  }
535 
540  public function getMain() {
541  return $this->mMainModule;
542  }
543 
549  public function isMain() {
550  return $this === $this->mMainModule;
551  }
552 
558  public function getParent() {
559  return $this->isMain() ? null : $this->getMain();
560  }
561 
572  public function lacksSameOriginSecurity() {
573  // Main module has this method overridden
574  // Safety - avoid infinite loop:
575  if ( $this->isMain() ) {
576  self::dieDebug( __METHOD__, 'base method was called on main module.' );
577  }
578 
579  return $this->getMain()->lacksSameOriginSecurity();
580  }
581 
588  public function getModulePath() {
589  if ( $this->isMain() ) {
590  return 'main';
591  } elseif ( $this->getParent()->isMain() ) {
592  return $this->getModuleName();
593  } else {
594  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
595  }
596  }
597 
606  public function getModuleFromPath( $path ) {
607  $module = $this->getMain();
608  if ( $path === 'main' ) {
609  return $module;
610  }
611 
612  $parts = explode( '+', $path );
613  if ( count( $parts ) === 1 ) {
614  // In case the '+' was typed into URL, it resolves as a space
615  $parts = explode( ' ', $path );
616  }
617 
618  $count = count( $parts );
619  for ( $i = 0; $i < $count; $i++ ) {
620  $parent = $module;
621  $manager = $parent->getModuleManager();
622  if ( $manager === null ) {
623  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
624  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
625  }
626  $module = $manager->getModule( $parts[$i] );
627 
628  if ( $module === null ) {
629  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
630  $this->dieWithError(
631  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
632  'badmodule'
633  );
634  }
635  }
636 
637  return $module;
638  }
639 
644  public function getResult() {
645  // Main module has getResult() method overridden
646  // Safety - avoid infinite loop:
647  if ( $this->isMain() ) {
648  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
649  }
650 
651  return $this->getMain()->getResult();
652  }
653 
658  public function getErrorFormatter() {
659  // Main module has getErrorFormatter() method overridden
660  // Safety - avoid infinite loop:
661  if ( $this->isMain() ) {
662  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
663  }
664 
665  return $this->getMain()->getErrorFormatter();
666  }
667 
672  protected function getDB() {
673  if ( !isset( $this->mReplicaDB ) ) {
674  $this->mReplicaDB = wfGetDB( DB_REPLICA, 'api' );
675  }
676 
677  return $this->mReplicaDB;
678  }
679 
684  public function getContinuationManager() {
685  // Main module has getContinuationManager() method overridden
686  // Safety - avoid infinite loop:
687  if ( $this->isMain() ) {
688  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
689  }
690 
691  return $this->getMain()->getContinuationManager();
692  }
693 
698  public function setContinuationManager( ApiContinuationManager $manager = null ) {
699  // Main module has setContinuationManager() method overridden
700  // Safety - avoid infinite loop:
701  if ( $this->isMain() ) {
702  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
703  }
704 
705  $this->getMain()->setContinuationManager( $manager );
706  }
707 
714  protected function getPermissionManager(): PermissionManager {
715  return MediaWikiServices::getInstance()->getPermissionManager();
716  }
717 
720  /************************************************************************/
732  public function dynamicParameterDocumentation() {
733  return null;
734  }
735 
743  public function encodeParamName( $paramName ) {
744  if ( is_array( $paramName ) ) {
745  return array_map( function ( $name ) {
746  return $this->mModulePrefix . $name;
747  }, $paramName );
748  } else {
749  return $this->mModulePrefix . $paramName;
750  }
751  }
752 
765  public function extractRequestParams( $options = [] ) {
766  if ( is_bool( $options ) ) {
767  $options = [ 'parseLimit' => $options ];
768  }
769  $options += [
770  'parseLimit' => true,
771  'safeMode' => false,
772  ];
773 
774  $parseLimit = (bool)$options['parseLimit'];
775  $cacheKey = (int)$parseLimit;
776 
777  // Cache parameters, for performance and to avoid T26564.
778  if ( !isset( $this->mParamCache[$cacheKey] ) ) {
779  $params = $this->getFinalParams() ?: [];
780  $results = [];
781  $warned = [];
782 
783  // Process all non-templates and save templates for secondary
784  // processing.
785  $toProcess = [];
786  foreach ( $params as $paramName => $paramSettings ) {
787  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
788  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
789  } else {
790  try {
791  $results[$paramName] = $this->getParameterFromSettings(
792  $paramName, $paramSettings, $parseLimit
793  );
794  } catch ( ApiUsageException $ex ) {
795  $results[$paramName] = $ex;
796  }
797  }
798  }
799 
800  // Now process all the templates by successively replacing the
801  // placeholders with all client-supplied values.
802  // This bit duplicates JavaScript logic in
803  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
804  // If you update this, see if that needs updating too.
805  while ( $toProcess ) {
806  list( $name, $targets, $settings ) = array_shift( $toProcess );
807 
808  foreach ( $targets as $placeholder => $target ) {
809  if ( !array_key_exists( $target, $results ) ) {
810  // The target wasn't processed yet, try the next one.
811  // If all hit this case, the parameter has no expansions.
812  continue;
813  }
814  if ( !is_array( $results[$target] ) || !$results[$target] ) {
815  // The target was processed but has no (valid) values.
816  // That means it has no expansions.
817  break;
818  }
819 
820  // Expand this target in the name and all other targets,
821  // then requeue if there are more targets left or put in
822  // $results if all are done.
823  unset( $targets[$placeholder] );
824  $placeholder = '{' . $placeholder . '}';
825  // @phan-suppress-next-line PhanTypeNoAccessiblePropertiesForeach
826  foreach ( $results[$target] as $value ) {
827  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
828  // Skip values that make invalid parameter names.
829  $encTargetName = $this->encodeParamName( $target );
830  if ( !isset( $warned[$encTargetName][$value] ) ) {
831  $warned[$encTargetName][$value] = true;
832  $this->addWarning( [
833  'apiwarn-ignoring-invalid-templated-value',
834  wfEscapeWikiText( $encTargetName ),
835  wfEscapeWikiText( $value ),
836  ] );
837  }
838  continue;
839  }
840 
841  $newName = str_replace( $placeholder, $value, $name );
842  if ( !$targets ) {
843  try {
844  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
845  } catch ( ApiUsageException $ex ) {
846  $results[$newName] = $ex;
847  }
848  } else {
849  $newTargets = [];
850  foreach ( $targets as $k => $v ) {
851  $newTargets[$k] = str_replace( $placeholder, $value, $v );
852  }
853  $toProcess[] = [ $newName, $newTargets, $settings ];
854  }
855  }
856  break;
857  }
858  }
859 
860  $this->mParamCache[$cacheKey] = $results;
861  }
862 
863  $ret = $this->mParamCache[$cacheKey];
864  if ( !$options['safeMode'] ) {
865  foreach ( $ret as $v ) {
866  if ( $v instanceof ApiUsageException ) {
867  throw $v;
868  }
869  }
870  }
871 
872  return $this->mParamCache[$cacheKey];
873  }
874 
881  protected function getParameter( $paramName, $parseLimit = true ) {
882  $ret = $this->extractRequestParams( [
883  'parseLimit' => $parseLimit,
884  'safeMode' => true,
885  ] )[$paramName];
886  if ( $ret instanceof ApiUsageException ) {
887  throw $ret;
888  }
889  return $ret;
890  }
891 
898  public function requireOnlyOneParameter( $params, ...$required ) {
899  $intersection = array_intersect( array_keys( array_filter( $params,
900  [ $this, 'parameterNotEmpty' ] ) ), $required );
901 
902  if ( count( $intersection ) > 1 ) {
903  $this->dieWithError( [
904  'apierror-invalidparammix',
905  Message::listParam( array_map(
906  function ( $p ) {
907  return '<var>' . $this->encodeParamName( $p ) . '</var>';
908  },
909  array_values( $intersection )
910  ) ),
911  count( $intersection ),
912  ] );
913  } elseif ( count( $intersection ) == 0 ) {
914  $this->dieWithError( [
915  'apierror-missingparam-one-of',
916  Message::listParam( array_map(
917  function ( $p ) {
918  return '<var>' . $this->encodeParamName( $p ) . '</var>';
919  },
920  $required
921  ) ),
922  count( $required ),
923  ], 'missingparam' );
924  }
925  }
926 
933  public function requireMaxOneParameter( $params, ...$required ) {
934  $intersection = array_intersect( array_keys( array_filter( $params,
935  [ $this, 'parameterNotEmpty' ] ) ), $required );
936 
937  if ( count( $intersection ) > 1 ) {
938  $this->dieWithError( [
939  'apierror-invalidparammix',
940  Message::listParam( array_map(
941  function ( $p ) {
942  return '<var>' . $this->encodeParamName( $p ) . '</var>';
943  },
944  array_values( $intersection )
945  ) ),
946  count( $intersection ),
947  ] );
948  }
949  }
950 
958  public function requireAtLeastOneParameter( $params, ...$required ) {
959  $intersection = array_intersect(
960  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
961  $required
962  );
963 
964  if ( count( $intersection ) == 0 ) {
965  $this->dieWithError( [
966  'apierror-missingparam-at-least-one-of',
967  Message::listParam( array_map(
968  function ( $p ) {
969  return '<var>' . $this->encodeParamName( $p ) . '</var>';
970  },
971  $required
972  ) ),
973  count( $required ),
974  ], 'missingparam' );
975  }
976  }
977 
985  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
986  // Skip if $wgDebugAPI is set or we're in internal mode
987  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
988  return;
989  }
990 
991  $queryValues = $this->getRequest()->getQueryValuesOnly();
992  $badParams = [];
993  foreach ( $params as $param ) {
994  if ( $prefix !== 'noprefix' ) {
995  $param = $this->encodeParamName( $param );
996  }
997  if ( array_key_exists( $param, $queryValues ) ) {
998  $badParams[] = $param;
999  }
1000  }
1001 
1002  if ( $badParams ) {
1003  $this->dieWithError(
1004  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
1005  );
1006  }
1007  }
1008 
1015  private function parameterNotEmpty( $x ) {
1016  return $x !== null && $x !== false;
1017  }
1018 
1030  public function getTitleOrPageId( $params, $load = false ) {
1031  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1032 
1033  $pageObj = null;
1034  if ( isset( $params['title'] ) ) {
1035  $titleObj = Title::newFromText( $params['title'] );
1036  if ( !$titleObj || $titleObj->isExternal() ) {
1037  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1038  }
1039  if ( !$titleObj->canExist() ) {
1040  $this->dieWithError( 'apierror-pagecannotexist' );
1041  }
1042  $pageObj = WikiPage::factory( $titleObj );
1043  if ( $load !== false ) {
1044  $pageObj->loadPageData( $load );
1045  }
1046  } elseif ( isset( $params['pageid'] ) ) {
1047  if ( $load === false ) {
1048  $load = 'fromdb';
1049  }
1050  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1051  if ( !$pageObj ) {
1052  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1053  }
1054  }
1055 
1056  return $pageObj;
1057  }
1058 
1067  public function getTitleFromTitleOrPageId( $params ) {
1068  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1069 
1070  $titleObj = null;
1071  if ( isset( $params['title'] ) ) {
1072  $titleObj = Title::newFromText( $params['title'] );
1073  if ( !$titleObj || $titleObj->isExternal() ) {
1074  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1075  }
1076  return $titleObj;
1077  } elseif ( isset( $params['pageid'] ) ) {
1078  $titleObj = Title::newFromID( $params['pageid'] );
1079  if ( !$titleObj ) {
1080  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1081  }
1082  }
1083 
1084  return $titleObj;
1085  }
1086 
1095  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1096  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1097 
1098  switch ( $watchlist ) {
1099  case 'watch':
1100  return true;
1101 
1102  case 'unwatch':
1103  return false;
1104 
1105  case 'preferences':
1106  # If the user is already watching, don't bother checking
1107  if ( $userWatching ) {
1108  return true;
1109  }
1110  # If no user option was passed, use watchdefault and watchcreations
1111  if ( $userOption === null ) {
1112  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1113  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1114  }
1115 
1116  # Watch the article based on the user preference
1117  return $this->getUser()->getBoolOption( $userOption );
1118 
1119  case 'nochange':
1120  return $userWatching;
1121 
1122  default:
1123  return $userWatching;
1124  }
1125  }
1126 
1136  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1137  // Some classes may decide to change parameter names
1138  $encParamName = $this->encodeParamName( $paramName );
1139 
1140  // Shorthand
1141  if ( !is_array( $paramSettings ) ) {
1142  $paramSettings = [
1143  self::PARAM_DFLT => $paramSettings,
1144  ];
1145  }
1146 
1147  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1148  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1149  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1150  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1151  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1152  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1153  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1154  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1155  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1156  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1157 
1158  // When type is not given, and no choices, the type is the same as $default
1159  if ( !isset( $type ) ) {
1160  if ( isset( $default ) ) {
1161  $type = gettype( $default );
1162  } else {
1163  $type = 'NULL'; // allow everything
1164  }
1165  }
1166 
1167  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1168  $this->getMain()->markParamsSensitive( $encParamName );
1169  }
1170 
1171  if ( $type == 'boolean' ) {
1172  if ( isset( $default ) && $default !== false ) {
1173  // Having a default value of anything other than 'false' is not allowed
1175  __METHOD__,
1176  "Boolean param $encParamName's default is set to '$default'. " .
1177  'Boolean parameters must default to false.'
1178  );
1179  }
1180 
1181  $value = $this->getMain()->getCheck( $encParamName );
1182  $provided = $value;
1183  } elseif ( $type == 'upload' ) {
1184  if ( isset( $default ) ) {
1185  // Having a default value is not allowed
1187  __METHOD__,
1188  "File upload param $encParamName's default is set to " .
1189  "'$default'. File upload parameters may not have a default." );
1190  }
1191  if ( $multi ) {
1192  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1193  }
1194  $value = $this->getMain()->getUpload( $encParamName );
1195  $provided = $value->exists();
1196  if ( !$value->exists() ) {
1197  // This will get the value without trying to normalize it
1198  // (because trying to normalize a large binary file
1199  // accidentally uploaded as a field fails spectacularly)
1200  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1201  if ( $value !== null ) {
1202  $this->dieWithError(
1203  [ 'apierror-badupload', $encParamName ],
1204  "badupload_{$encParamName}"
1205  );
1206  }
1207  }
1208  } else {
1209  $value = $this->getMain()->getVal( $encParamName, $default );
1210  $provided = $this->getMain()->getCheck( $encParamName );
1211 
1212  if ( isset( $value ) && $type == 'namespace' ) {
1213  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
1214  getValidNamespaces();
1215  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1216  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1217  ) {
1218  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1219  }
1220  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1221  // specify all namespaces irrespective of PARAM_ALL.
1222  $allowAll = true;
1223  }
1224  if ( isset( $value ) && $type == 'submodule' ) {
1225  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1226  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1227  } else {
1228  $type = $this->getModuleManager()->getNames( $paramName );
1229  }
1230  }
1231 
1232  $request = $this->getMain()->getRequest();
1233  $rawValue = $request->getRawVal( $encParamName );
1234  if ( $rawValue === null ) {
1235  $rawValue = $default;
1236  }
1237 
1238  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1239  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1240  if ( $multi ) {
1241  // This loses the potential checkTitleEncoding() transformation done by
1242  // WebRequest for $_GET. Let's call that a feature.
1243  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1244  } else {
1245  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1246  }
1247  }
1248 
1249  // Check for NFC normalization, and warn
1250  if ( $rawValue !== $value ) {
1251  $this->handleParamNormalization( $paramName, $value, $rawValue );
1252  }
1253  }
1254 
1255  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1256  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1258  __METHOD__,
1259  "For param $encParamName, PARAM_ALL collides with a possible value" );
1260  }
1261  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1262  $value = $this->parseMultiValue(
1263  $encParamName,
1264  $value,
1265  $multi,
1266  is_array( $type ) ? $type : null,
1267  $allowAll ? $allSpecifier : null,
1268  $multiLimit1,
1269  $multiLimit2
1270  );
1271  }
1272 
1273  if ( isset( $value ) ) {
1274  // More validation only when choices were not given
1275  // choices were validated in parseMultiValue()
1276  if ( !is_array( $type ) ) {
1277  switch ( $type ) {
1278  case 'NULL': // nothing to do
1279  break;
1280  case 'string':
1281  case 'text':
1282  case 'password':
1283  if ( $required && $value === '' ) {
1284  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1285  }
1286  break;
1287  case 'integer': // Force everything using intval() and optionally validate limits
1288  $min = $paramSettings[self::PARAM_MIN] ?? null;
1289  $max = $paramSettings[self::PARAM_MAX] ?? null;
1290  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1291 
1292  if ( is_array( $value ) ) {
1293  $value = array_map( 'intval', $value );
1294  if ( $min !== null || $max !== null ) {
1295  foreach ( $value as &$v ) {
1296  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1297  }
1298  }
1299  } else {
1300  $value = (int)$value;
1301  if ( $min !== null || $max !== null ) {
1302  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1303  }
1304  }
1305  break;
1306  case 'limit':
1307  // Must be a number or 'max'
1308  if ( $value !== 'max' ) {
1309  $value = (int)$value;
1310  }
1311  if ( $multi ) {
1312  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1313  }
1314  if ( !$parseLimit ) {
1315  // Don't do min/max validation and don't parse 'max'
1316  break;
1317  }
1318  if ( !isset( $paramSettings[self::PARAM_MAX] )
1319  || !isset( $paramSettings[self::PARAM_MAX2] )
1320  ) {
1322  __METHOD__,
1323  "MAX1 or MAX2 are not defined for the limit $encParamName"
1324  );
1325  }
1326  if ( $value === 'max' ) {
1327  $value = $this->getMain()->canApiHighLimits()
1328  ? $paramSettings[self::PARAM_MAX2]
1329  : $paramSettings[self::PARAM_MAX];
1330  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1331  } else {
1332  $this->validateLimit(
1333  $paramName,
1334  $value,
1335  $paramSettings[self::PARAM_MIN] ?? 0,
1336  $paramSettings[self::PARAM_MAX],
1337  $paramSettings[self::PARAM_MAX2]
1338  );
1339  }
1340  break;
1341  case 'boolean':
1342  if ( $multi ) {
1343  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1344  }
1345  break;
1346  case 'timestamp':
1347  if ( is_array( $value ) ) {
1348  foreach ( $value as $key => $val ) {
1349  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1350  }
1351  } else {
1352  $value = $this->validateTimestamp( $value, $encParamName );
1353  }
1354  break;
1355  case 'user':
1356  if ( is_array( $value ) ) {
1357  foreach ( $value as $key => $val ) {
1358  $value[$key] = $this->validateUser( $val, $encParamName );
1359  }
1360  } else {
1361  $value = $this->validateUser( $value, $encParamName );
1362  }
1363  break;
1364  case 'upload': // nothing to do
1365  break;
1366  case 'tags':
1367  // If change tagging was requested, check that the tags are valid.
1368  if ( !is_array( $value ) && !$multi ) {
1369  $value = [ $value ];
1370  }
1371  $tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $value );
1372  if ( !$tagsStatus->isGood() ) {
1373  $this->dieStatus( $tagsStatus );
1374  }
1375  break;
1376  default:
1377  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1378  }
1379  }
1380 
1381  // Throw out duplicates if requested
1382  if ( !$dupes && is_array( $value ) ) {
1383  $value = array_unique( $value );
1384  }
1385 
1386  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1387  foreach ( (array)$value as $val ) {
1388  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1389  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1390  ) {
1391  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1392  $paramSettings[self::PARAM_MAX_BYTES] ] );
1393  }
1394  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1395  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1396  ) {
1397  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1398  $paramSettings[self::PARAM_MAX_CHARS] ] );
1399  }
1400  }
1401  }
1402 
1403  // Set a warning if a deprecated parameter has been passed
1404  if ( $deprecated && $provided ) {
1405  $feature = $encParamName;
1406  $m = $this;
1407  while ( !$m->isMain() ) {
1408  $p = $m->getParent();
1409  $name = $m->getModuleName();
1410  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1411  $feature = "{$param}={$name}&{$feature}";
1412  $m = $p;
1413  }
1414  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1415  }
1416 
1417  // Set a warning if a deprecated parameter value has been passed
1418  $usedDeprecatedValues = $deprecatedValues && $provided
1419  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1420  : [];
1421  if ( $usedDeprecatedValues ) {
1422  $feature = "$encParamName=";
1423  $m = $this;
1424  while ( !$m->isMain() ) {
1425  $p = $m->getParent();
1426  $name = $m->getModuleName();
1427  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1428  $feature = "{$param}={$name}&{$feature}";
1429  $m = $p;
1430  }
1431  foreach ( $usedDeprecatedValues as $v ) {
1432  // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
1433  $msg = $deprecatedValues[$v];
1434  if ( $msg === true ) {
1435  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1436  }
1437  $this->addDeprecation( $msg, "$feature$v" );
1438  }
1439  }
1440  } elseif ( $required ) {
1441  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1442  }
1443 
1444  return $value;
1445  }
1446 
1454  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1455  $encParamName = $this->encodeParamName( $paramName );
1456  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1457  }
1458 
1466  protected function explodeMultiValue( $value, $limit ) {
1467  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1468  $sep = "\x1f";
1469  $value = substr( $value, 1 );
1470  } else {
1471  $sep = '|';
1472  }
1473 
1474  return explode( $sep, $value, $limit );
1475  }
1476 
1494  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1495  $allSpecifier = null, $limit1 = null, $limit2 = null
1496  ) {
1497  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1498  return [];
1499  }
1500  $limit1 = $limit1 ?: self::LIMIT_SML1;
1501  $limit2 = $limit2 ?: self::LIMIT_SML2;
1502 
1503  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1504  // because it unstubs $wgUser
1505  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1506  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1507  ? $limit2
1508  : $limit1;
1509 
1510  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1511  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1512  ) {
1513  return $allowedValues;
1514  }
1515 
1516  if ( count( $valuesList ) > $sizeLimit ) {
1517  $this->dieWithError(
1518  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1519  "too-many-$valueName"
1520  );
1521  }
1522 
1523  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1524  // T35482 - Allow entries with | in them for non-multiple values
1525  if ( in_array( $value, $allowedValues, true ) ) {
1526  return $value;
1527  }
1528 
1529  $values = array_map( function ( $v ) {
1530  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1531  }, $allowedValues );
1532  $this->dieWithError( [
1533  'apierror-multival-only-one-of',
1534  $valueName,
1535  Message::listParam( $values ),
1536  count( $values ),
1537  ], "multival_$valueName" );
1538  }
1539 
1540  if ( is_array( $allowedValues ) ) {
1541  // Check for unknown values
1542  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1543  if ( count( $unknown ) ) {
1544  if ( $allowMultiple ) {
1545  $this->addWarning( [
1546  'apiwarn-unrecognizedvalues',
1547  $valueName,
1548  Message::listParam( $unknown, 'comma' ),
1549  count( $unknown ),
1550  ] );
1551  } else {
1552  $this->dieWithError(
1553  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1554  "unknown_$valueName"
1555  );
1556  }
1557  }
1558  // Now throw them out
1559  $valuesList = array_intersect( $valuesList, $allowedValues );
1560  }
1561 
1562  return $allowMultiple ? $valuesList : $valuesList[0];
1563  }
1564 
1575  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1576  $enforceLimits = false
1577  ) {
1578  if ( $min !== null && $value < $min ) {
1579  $msg = ApiMessage::create(
1580  [ 'apierror-integeroutofrange-belowminimum',
1581  $this->encodeParamName( $paramName ), $min, $value ],
1582  'integeroutofrange',
1583  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1584  );
1585  // @phan-suppress-next-line PhanTypeMismatchArgument
1586  $this->warnOrDie( $msg, $enforceLimits );
1587  $value = $min;
1588  }
1589 
1590  // Minimum is always validated, whereas maximum is checked only if not
1591  // running in internal call mode
1592  if ( $this->getMain()->isInternalMode() ) {
1593  return;
1594  }
1595 
1596  // Optimization: do not check user's bot status unless really needed -- skips db query
1597  // assumes $botMax >= $max
1598  if ( $max !== null && $value > $max ) {
1599  if ( $botMax !== null && $this->getMain()->canApiHighLimits() ) {
1600  if ( $value > $botMax ) {
1601  $msg = ApiMessage::create(
1602  [ 'apierror-integeroutofrange-abovebotmax',
1603  $this->encodeParamName( $paramName ), $botMax, $value ],
1604  'integeroutofrange',
1605  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1606  );
1607  // @phan-suppress-next-line PhanTypeMismatchArgument
1608  $this->warnOrDie( $msg, $enforceLimits );
1609  $value = $botMax;
1610  }
1611  } else {
1612  $msg = ApiMessage::create(
1613  [ 'apierror-integeroutofrange-abovemax',
1614  $this->encodeParamName( $paramName ), $max, $value ],
1615  'integeroutofrange',
1616  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1617  );
1618  // @phan-suppress-next-line PhanTypeMismatchArgument
1619  $this->warnOrDie( $msg, $enforceLimits );
1620  $value = $max;
1621  }
1622  }
1623  }
1624 
1631  protected function validateTimestamp( $value, $encParamName ) {
1632  // Confusing synonyms for the current time accepted by wfTimestamp()
1633  // (wfTimestamp() also accepts various non-strings and the string of 14
1634  // ASCII NUL bytes, but those can't get here)
1635  if ( !$value ) {
1636  $this->addDeprecation(
1637  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1638  'unclear-"now"-timestamp'
1639  );
1640  return wfTimestamp( TS_MW );
1641  }
1642 
1643  // Explicit synonym for the current time
1644  if ( $value === 'now' ) {
1645  return wfTimestamp( TS_MW );
1646  }
1647 
1648  $timestamp = wfTimestamp( TS_MW, $value );
1649  if ( $timestamp === false ) {
1650  $this->dieWithError(
1651  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1652  "badtimestamp_{$encParamName}"
1653  );
1654  }
1655 
1656  return $timestamp;
1657  }
1658 
1668  final public function validateToken( $token, array $params ) {
1669  $tokenType = $this->needsToken();
1671  if ( !isset( $salts[$tokenType] ) ) {
1672  throw new MWException(
1673  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1674  'without registering it'
1675  );
1676  }
1677 
1678  $tokenObj = ApiQueryTokens::getToken(
1679  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1680  );
1681  if ( $tokenObj->match( $token ) ) {
1682  return true;
1683  }
1684 
1685  $webUiSalt = $this->getWebUITokenSalt( $params );
1686  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1687  $token,
1688  $webUiSalt,
1689  $this->getRequest()
1690  ) ) {
1691  return true;
1692  }
1693 
1694  return false;
1695  }
1696 
1703  private function validateUser( $value, $encParamName ) {
1704  if ( ExternalUserNames::isExternal( $value ) && User::newFromName( $value, false ) ) {
1705  return $value;
1706  }
1707 
1708  $name = User::getCanonicalName( $value, 'valid' );
1709  if ( $name !== false ) {
1710  return $name;
1711  }
1712 
1713  if (
1714  IPUtils::isIPAddress( $value ) ||
1715  // We allow ranges as well, for blocks.
1716  IPUtils::isValidRange( $value ) ||
1717  // See comment for User::isIP. We don't just call that function
1718  // here because it also returns true for things like
1719  // 300.300.300.300 that are neither valid usernames nor valid IP
1720  // addresses.
1721  preg_match(
1722  '/^' . IPUtils::RE_IP_BYTE .
1723  '\.' . IPUtils::RE_IP_BYTE .
1724  '\.' . IPUtils::RE_IP_BYTE .
1725  '\.xxx$/',
1726  $value
1727  )
1728  ) {
1729  return IPUtils::sanitizeIP( $value );
1730  }
1731 
1732  $this->dieWithError(
1733  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1734  "baduser_{$encParamName}"
1735  );
1736  }
1737 
1740  /************************************************************************/
1751  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1752  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1753  if ( $value === null ) {
1754  return;
1755  }
1756 
1757  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1758  }
1759 
1766  public function getWatchlistUser( $params ) {
1767  if ( $params['owner'] !== null && $params['token'] !== null ) {
1768  $user = User::newFromName( $params['owner'], false );
1769  if ( !( $user && $user->getId() ) ) {
1770  $this->dieWithError(
1771  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1772  );
1773  }
1774  $token = $user->getOption( 'watchlisttoken' );
1775  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1776  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1777  }
1778  } else {
1779  if ( !$this->getUser()->isLoggedIn() ) {
1780  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1781  }
1782  $this->checkUserRightsAny( 'viewmywatchlist' );
1783  $user = $this->getUser();
1784  }
1785 
1786  return $user;
1787  }
1788 
1801  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1802  if ( is_string( $msg ) ) {
1803  $msg = wfMessage( $msg );
1804  } elseif ( is_array( $msg ) ) {
1805  $msg = wfMessage( ...$msg );
1806  }
1807  if ( !$msg instanceof Message ) {
1808  return null;
1809  }
1810 
1811  $msg->setContext( $context );
1812  if ( $params ) {
1813  $msg->params( $params );
1814  }
1815 
1816  return $msg;
1817  }
1818 
1826  public function errorArrayToStatus( array $errors, User $user = null ) {
1827  if ( $user === null ) {
1828  $user = $this->getUser();
1829  }
1830 
1831  $status = Status::newGood();
1832  foreach ( $errors as $error ) {
1833  if ( !is_array( $error ) ) {
1834  $error = [ $error ];
1835  }
1836  if ( is_string( $error[0] ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
1837  list( $msg, $code ) = self::$blockMsgMap[$error[0]];
1838  $status->fatal( ApiMessage::create( $msg, $code,
1839  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1840  ) );
1841  } else {
1842  $status->fatal( ...$error );
1843  }
1844  }
1845  return $status;
1846  }
1847 
1854  public function addBlockInfoToStatus( StatusValue $status, User $user = null ) {
1855  if ( $user === null ) {
1856  $user = $this->getUser();
1857  }
1858 
1859  foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
1860  if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
1861  $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
1862  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1863  ) );
1864  }
1865  }
1866  }
1867 
1872  protected function useTransactionalTimeLimit() {
1873  if ( $this->getRequest()->wasPosted() ) {
1875  }
1876  }
1877 
1886  protected function filterIDs( $fields, array $ids ) {
1887  $min = INF;
1888  $max = 0;
1889  foreach ( $fields as list( $table, $field ) ) {
1890  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1891  $row = self::$filterIDsCache[$table][$field];
1892  } else {
1893  $row = $this->getDB()->selectRow(
1894  $table,
1895  [
1896  'min_id' => "MIN($field)",
1897  'max_id' => "MAX($field)",
1898  ],
1899  '',
1900  __METHOD__
1901  );
1902  self::$filterIDsCache[$table][$field] = $row;
1903  }
1904  $min = min( $min, $row->min_id );
1905  $max = max( $max, $row->max_id );
1906  }
1907  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1908  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1909  && $id >= $min && $id <= $max;
1910  } );
1911  }
1912 
1915  /************************************************************************/
1934  public function addWarning( $msg, $code = null, $data = null ) {
1935  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1936  }
1937 
1948  public function addDeprecation( $msg, $feature, $data = [] ) {
1949  $data = (array)$data;
1950  if ( $feature !== null ) {
1951  $data['feature'] = $feature;
1952  $this->logFeatureUsage( $feature );
1953  }
1954  $this->addWarning( $msg, 'deprecation', $data );
1955 
1956  // No real need to deduplicate here, ApiErrorFormatter does that for
1957  // us (assuming the hook is deterministic).
1958  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1959  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1960  if ( count( $msgs ) > 1 ) {
1961  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1962  $msg = ( new RawMessage( $key ) )->params( $msgs );
1963  } else {
1964  $msg = reset( $msgs );
1965  }
1966  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1967  }
1968 
1981  public function addError( $msg, $code = null, $data = null ) {
1982  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1983  }
1984 
1994  public function addMessagesFromStatus(
1995  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1996  ) {
1997  $this->getErrorFormatter()->addMessagesFromStatus(
1998  $this->getModulePath(), $status, $types, $filter
1999  );
2000  }
2001 
2015  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
2016  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
2017  }
2018 
2027  public function dieWithException( $exception, array $options = [] ) {
2028  $this->dieWithError(
2029  // @phan-suppress-next-line PhanTypeMismatchArgument
2030  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
2031  );
2032  }
2033 
2040  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2041  if ( $enforceLimits ) {
2042  $this->dieWithError( $msg );
2043  } else {
2044  $this->addWarning( $msg );
2045  }
2046  }
2047 
2056  public function dieBlocked( AbstractBlock $block ) {
2057  // Die using the appropriate message depending on block type
2058  if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
2059  $this->dieWithError(
2060  'apierror-autoblocked',
2061  'autoblocked',
2062  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2063  );
2064  } elseif ( !$block->isSitewide() ) {
2065  $this->dieWithError(
2066  'apierror-blocked-partial',
2067  'blocked',
2068  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2069  );
2070  } else {
2071  $this->dieWithError(
2072  'apierror-blocked',
2073  'blocked',
2074  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2075  );
2076  }
2077  }
2078 
2087  public function dieStatus( StatusValue $status ) {
2088  if ( $status->isGood() ) {
2089  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2090  }
2091 
2092  // ApiUsageException needs a fatal status, but this method has
2093  // historically accepted any non-good status. Convert it if necessary.
2094  $status->setOK( false );
2095  if ( !$status->getErrorsByType( 'error' ) ) {
2096  $newStatus = Status::newGood();
2097  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2098  $newStatus->fatal( $err['message'], ...$err['params'] );
2099  }
2100  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2101  $newStatus->fatal( 'unknownerror-nocode' );
2102  }
2103  $status = $newStatus;
2104  }
2105 
2106  $this->addBlockInfoToStatus( $status );
2107  throw new ApiUsageException( $this, $status );
2108  }
2109 
2115  public function dieReadOnly() {
2116  $this->dieWithError(
2117  'apierror-readonly',
2118  'readonly',
2119  [ 'readonlyreason' => wfReadOnlyReason() ]
2120  );
2121  }
2122 
2131  public function checkUserRightsAny( $rights, $user = null ) {
2132  if ( !$user ) {
2133  $user = $this->getUser();
2134  }
2135  $rights = (array)$rights;
2136  if ( !$this->getPermissionManager()
2137  ->userHasAnyRight( $user, ...$rights )
2138  ) {
2139  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2140  }
2141  }
2142 
2157  public function checkTitleUserPermissions( LinkTarget $linkTarget, $actions, $options = [] ) {
2158  if ( !is_array( $options ) ) {
2159  wfDeprecated( '$user as the third parameter to ' . __METHOD__, '1.33' );
2160  $options = [ 'user' => $options ];
2161  }
2162  $user = $options['user'] ?? $this->getUser();
2163 
2164  $errors = [];
2165  foreach ( (array)$actions as $action ) {
2166  $errors = array_merge(
2167  $errors,
2168  $this->getPermissionManager()->getPermissionErrors( $action, $user, $linkTarget )
2169  );
2170  }
2171 
2172  if ( $errors ) {
2173  if ( !empty( $options['autoblock'] ) ) {
2174  $user->spreadAnyEditBlock();
2175  }
2176 
2177  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2178  }
2179  }
2180 
2192  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2193  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2194  $this->dieWithError( $msg, $code, $data, $httpCode );
2195  } else {
2196  $this->addWarning( $msg, $code, $data );
2197  }
2198  }
2199 
2210  protected function dieContinueUsageIf( $condition ) {
2211  if ( $condition ) {
2212  $this->dieWithError( 'apierror-badcontinue' );
2213  }
2214  }
2215 
2222  protected static function dieDebug( $method, $message ) {
2223  throw new MWException( "Internal error in $method: $message" );
2224  }
2225 
2232  public function logFeatureUsage( $feature ) {
2233  static $loggedFeatures = [];
2234 
2235  // Only log each feature once per request. We can get multiple calls from calls to
2236  // extractRequestParams() with different values for 'parseLimit', for example.
2237  if ( isset( $loggedFeatures[$feature] ) ) {
2238  return;
2239  }
2240  $loggedFeatures[$feature] = true;
2241 
2242  $request = $this->getRequest();
2243  $ctx = [
2244  'feature' => $feature,
2245  // Spaces to underscores in 'username' for historical reasons.
2246  'username' => str_replace( ' ', '_', $this->getUser()->getName() ),
2247  'ip' => $request->getIP(),
2248  'referer' => (string)$request->getHeader( 'Referer' ),
2249  'agent' => $this->getMain()->getUserAgent(),
2250  ];
2251 
2252  // Text string is deprecated. Remove (or replace with just $feature) in MW 1.34.
2253  $s = '"' . addslashes( $ctx['feature'] ) . '"' .
2254  ' "' . wfUrlencode( $ctx['username'] ) . '"' .
2255  ' "' . $ctx['ip'] . '"' .
2256  ' "' . addslashes( $ctx['referer'] ) . '"' .
2257  ' "' . addslashes( $ctx['agent'] ) . '"';
2258 
2259  wfDebugLog( 'api-feature-usage', $s, 'private', $ctx );
2260  }
2261 
2264  /************************************************************************/
2278  protected function getSummaryMessage() {
2279  return "apihelp-{$this->getModulePath()}-summary";
2280  }
2281 
2291  protected function getExtendedDescription() {
2292  return [ [
2293  "apihelp-{$this->getModulePath()}-extended-description",
2294  'api-help-no-extended-description',
2295  ] ];
2296  }
2297 
2304  public function getFinalSummary() {
2305  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2306  $this->getModulePrefix(),
2307  $this->getModuleName(),
2308  $this->getModulePath(),
2309  ] );
2310  return $msg;
2311  }
2312 
2320  public function getFinalDescription() {
2321  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2322  $this->getModulePrefix(),
2323  $this->getModuleName(),
2324  $this->getModulePath(),
2325  ] );
2326  $extendedDescription = self::makeMessage(
2327  $this->getExtendedDescription(), $this->getContext(), [
2328  $this->getModulePrefix(),
2329  $this->getModuleName(),
2330  $this->getModulePath(),
2331  ]
2332  );
2333 
2334  $msgs = [ $summary, $extendedDescription ];
2335 
2336  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2337 
2338  return $msgs;
2339  }
2340 
2349  public function getFinalParams( $flags = 0 ) {
2350  // @phan-suppress-next-line PhanParamTooMany
2351  $params = $this->getAllowedParams( $flags );
2352  if ( !$params ) {
2353  $params = [];
2354  }
2355 
2356  if ( $this->needsToken() ) {
2357  $params['token'] = [
2358  self::PARAM_TYPE => 'string',
2359  self::PARAM_REQUIRED => true,
2360  self::PARAM_SENSITIVE => true,
2361  self::PARAM_HELP_MSG => [
2362  'api-help-param-token',
2363  $this->needsToken(),
2364  ],
2365  ] + ( $params['token'] ?? [] );
2366  }
2367 
2368  // Avoid PHP 7.1 warning of passing $this by reference
2369  $apiModule = $this;
2370  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2371 
2372  return $params;
2373  }
2374 
2382  public function getFinalParamDescription() {
2383  $prefix = $this->getModulePrefix();
2384  $name = $this->getModuleName();
2385  $path = $this->getModulePath();
2386 
2387  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2388  $msgs = [];
2389  foreach ( $params as $param => $settings ) {
2390  if ( !is_array( $settings ) ) {
2391  $settings = [];
2392  }
2393 
2394  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2395  $msg = $settings[self::PARAM_HELP_MSG];
2396  } else {
2397  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2398  }
2399  $msg = self::makeMessage( $msg, $this->getContext(),
2400  [ $prefix, $param, $name, $path ] );
2401  if ( !$msg ) {
2402  self::dieDebug( __METHOD__,
2403  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2404  }
2405  $msgs[$param] = [ $msg ];
2406 
2407  if ( isset( $settings[self::PARAM_TYPE] ) &&
2408  $settings[self::PARAM_TYPE] === 'submodule'
2409  ) {
2410  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2411  $map = $settings[self::PARAM_SUBMODULE_MAP];
2412  } else {
2413  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2414  $map = [];
2415  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2416  $map[$submoduleName] = $prefix . $submoduleName;
2417  }
2418  }
2419 
2420  $submodules = [];
2421  $submoduleFlags = []; // for sorting: higher flags are sorted later
2422  $submoduleNames = []; // for sorting: lexicographical, ascending
2423  foreach ( $map as $v => $m ) {
2424  $isDeprecated = false;
2425  $isInternal = false;
2426  $summary = null;
2427  try {
2428  $submod = $this->getModuleFromPath( $m );
2429  if ( $submod ) {
2430  $summary = $submod->getFinalSummary();
2431  $isDeprecated = $submod->isDeprecated();
2432  $isInternal = $submod->isInternal();
2433  }
2434  } catch ( ApiUsageException $ex ) {
2435  // Ignore
2436  }
2437  if ( $summary ) {
2438  $key = $summary->getKey();
2439  $params = $summary->getParams();
2440  } else {
2441  $key = 'api-help-undocumented-module';
2442  $params = [ $m ];
2443  }
2444  $m = new ApiHelpParamValueMessage(
2445  "[[Special:ApiHelp/$m|$v]]",
2446  $key,
2447  $params,
2448  $isDeprecated,
2449  $isInternal
2450  );
2451  $submodules[] = $m->setContext( $this->getContext() );
2452  $submoduleFlags[] = ( $isDeprecated ? 1 : 0 ) | ( $isInternal ? 2 : 0 );
2453  $submoduleNames[] = $v;
2454  }
2455  // sort $submodules by $submoduleFlags and $submoduleNames
2456  array_multisort( $submoduleFlags, $submoduleNames, $submodules );
2457  $msgs[$param] = array_merge( $msgs[$param], $submodules );
2458  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2459  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2460  self::dieDebug( __METHOD__,
2461  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2462  }
2463  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2464  self::dieDebug( __METHOD__,
2465  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2466  'ApiBase::PARAM_TYPE is an array' );
2467  }
2468 
2469  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2470  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2471 
2472  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2473  if ( isset( $valueMsgs[$value] ) ) {
2474  $msg = $valueMsgs[$value];
2475  } else {
2476  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2477  }
2478  $m = self::makeMessage( $msg, $this->getContext(),
2479  [ $prefix, $param, $name, $path, $value ] );
2480  if ( $m ) {
2481  $m = new ApiHelpParamValueMessage(
2482  $value,
2483  // @phan-suppress-next-line PhanTypeMismatchArgument
2484  [ $m->getKey(), 'api-help-param-no-description' ],
2485  $m->getParams(),
2486  isset( $deprecatedValues[$value] )
2487  );
2488  $msgs[$param][] = $m->setContext( $this->getContext() );
2489  } else {
2490  self::dieDebug( __METHOD__,
2491  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2492  }
2493  }
2494  }
2495 
2496  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2497  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2498  self::dieDebug( __METHOD__,
2499  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2500  }
2501  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2502  $m = self::makeMessage( $m, $this->getContext(),
2503  [ $prefix, $param, $name, $path ] );
2504  if ( $m ) {
2505  $msgs[$param][] = $m;
2506  } else {
2507  self::dieDebug( __METHOD__,
2508  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2509  }
2510  }
2511  }
2512  }
2513 
2514  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2515 
2516  return $msgs;
2517  }
2518 
2528  protected function getHelpFlags() {
2529  $flags = [];
2530 
2531  if ( $this->isDeprecated() ) {
2532  $flags[] = 'deprecated';
2533  }
2534  if ( $this->isInternal() ) {
2535  $flags[] = 'internal';
2536  }
2537  if ( $this->isReadMode() ) {
2538  $flags[] = 'readrights';
2539  }
2540  if ( $this->isWriteMode() ) {
2541  $flags[] = 'writerights';
2542  }
2543  if ( $this->mustBePosted() ) {
2544  $flags[] = 'mustbeposted';
2545  }
2546 
2547  return $flags;
2548  }
2549 
2561  protected function getModuleSourceInfo() {
2562  global $IP;
2563 
2564  if ( $this->mModuleSource !== false ) {
2565  return $this->mModuleSource;
2566  }
2567 
2568  // First, try to find where the module comes from...
2569  $rClass = new ReflectionClass( $this );
2570  $path = $rClass->getFileName();
2571  if ( !$path ) {
2572  // No path known?
2573  $this->mModuleSource = null;
2574  return null;
2575  }
2576  $path = realpath( $path ) ?: $path;
2577 
2578  // Build map of extension directories to extension info
2579  if ( self::$extensionInfo === null ) {
2580  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2581  self::$extensionInfo = [
2582  realpath( __DIR__ ) ?: __DIR__ => [
2583  'path' => $IP,
2584  'name' => 'MediaWiki',
2585  'license-name' => 'GPL-2.0-or-later',
2586  ],
2587  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2588  realpath( $extDir ) ?: $extDir => null,
2589  ];
2590  $keep = [
2591  'path' => null,
2592  'name' => null,
2593  'namemsg' => null,
2594  'license-name' => null,
2595  ];
2596  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2597  foreach ( $group as $ext ) {
2598  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2599  // This shouldn't happen, but does anyway.
2600  continue;
2601  }
2602 
2603  $extpath = $ext['path'];
2604  if ( !is_dir( $extpath ) ) {
2605  $extpath = dirname( $extpath );
2606  }
2607  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2608  array_intersect_key( $ext, $keep );
2609  }
2610  }
2611  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2612  $extpath = $ext['path'];
2613  if ( !is_dir( $extpath ) ) {
2614  $extpath = dirname( $extpath );
2615  }
2616  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2617  array_intersect_key( $ext, $keep );
2618  }
2619  }
2620 
2621  // Now traverse parent directories until we find a match or run out of
2622  // parents.
2623  do {
2624  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2625  // Found it!
2626  $this->mModuleSource = self::$extensionInfo[$path];
2627  return $this->mModuleSource;
2628  }
2629 
2630  $oldpath = $path;
2631  $path = dirname( $path );
2632  } while ( $path !== $oldpath );
2633 
2634  // No idea what extension this might be.
2635  $this->mModuleSource = null;
2636  return null;
2637  }
2638 
2650  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2651  }
2652 
2654 }
2655 
ApiMain
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
ContextSource\$context
IContextSource $context
Definition: ContextSource.php:33
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:63
ApiBase\PARAM_SUBMODULE_MAP
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is 'submodule', map parameter values to submodule paths.
Definition: ApiBase.php:173
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:317
ApiUsageException
Exception used to abort API execution with an error.
Definition: ApiUsageException.php:28
ContextSource\getContext
getContext()
Get the base IContextSource object.
Definition: ContextSource.php:40
ApiBase\$mMainModule
ApiMain $mMainModule
Definition: ApiBase.php:291
StatusValue
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: StatusValue.php:42
ApiBase\getParent
getParent()
Get the parent of this module.
Definition: ApiBase.php:558
ApiBase\addWarning
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1934
ApiBase\getSummaryMessage
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2278
ApiBase\getFinalParams
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2349
ApiBase\PARAM_REQUIRED
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition: ApiBase.php:119
StatusValue\getErrorsByType
getErrorsByType( $type)
Returns a list of status messages of the given type.
Definition: StatusValue.php:243
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:130
ApiBase\parameterNotEmpty
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set.
Definition: ApiBase.php:1015
ApiBase\$mModuleSource
array null bool $mModuleSource
Definition: ApiBase.php:300
ApiBase\validateToken
validateToken( $token, array $params)
Validate the supplied token.
Definition: ApiBase.php:1668
ApiContinuationManager
This manages continuation state.
Definition: ApiContinuationManager.php:26
ApiBase\dieWithError
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2015
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:132
ApiBase\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:373
StatusValue\replaceMessage
replaceMessage( $source, $dest)
If the specified source message exists, replace it with the specified destination message,...
Definition: StatusValue.php:289
ApiBase\PARAM_ALL
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true,...
Definition: ApiBase.php:188
ApiBase\validateTimestamp
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type 'timestamp'.
Definition: ApiBase.php:1631
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1871
ApiBase\$extensionInfo
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php:276
ApiBase\getTitleOrPageId
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1030
ApiBase\PARAM_TYPE
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition: ApiBase.php:95
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:644
ApiBase\__construct
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:307
ApiBase\$blockMsgMap
static $blockMsgMap
$var array Map of web UI block messages to corresponding API messages and codes
Definition: ApiBase.php:282
wfUrlencode
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
Definition: GlobalFunctions.php:309
ApiBase\mustBePosted
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:439
ApiBase\logFeatureUsage
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2232
ApiBase\checkUserRightsAny
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2131
ApiBase\shouldCheckMaxlag
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:408
ApiBase\dieWithErrorOrDebug
dieWithErrorOrDebug( $msg, $code=null, $data=null, $httpCode=null)
Will only set a warning instead of failing if the global $wgDebugAPI is set to true.
Definition: ApiBase.php:2192
ApiBase\setContinuationManager
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:698
ApiBase\getExtendedDescription
getExtendedDescription()
Return the extended help text message.
Definition: ApiBase.php:2291
ApiBase\PARAM_ISMULTI_LIMIT1
const PARAM_ISMULTI_LIMIT1
(integer) Maximum number of values, for normal users.
Definition: ApiBase.php:216
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:537
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1263
ApiBase\addBlockInfoToStatus
addBlockInfoToStatus(StatusValue $status, User $user=null)
Add block info to block messages in a Status.
Definition: ApiBase.php:1854
$s
$s
Definition: mergeMessageFileList.php:185
ApiBase\dieBlocked
dieBlocked(AbstractBlock $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition: ApiBase.php:2056
ApiBase\getDB
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:672
ApiBase\addMessagesFromStatus
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1994
ApiBase\makeMessage
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition: ApiBase.php:1801
ContextSource\getRequest
getRequest()
Definition: ContextSource.php:71
ApiBase\checkTitleUserPermissions
checkTitleUserPermissions(LinkTarget $linkTarget, $actions, $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:2157
ApiBase\dynamicParameterDocumentation
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:732
ApiBase\modifyHelp
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition: ApiBase.php:2650
ApiBase\PARAM_ALLOW_DUPLICATES
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition: ApiBase.php:110
Message
ApiUsageException\newWithMessage
static newWithMessage(?ApiBase $module, $msg, $code=null, $data=null, $httpCode=0)
Definition: ApiUsageException.php:63
ContextSource\getUser
getUser()
Definition: ContextSource.php:120
StatusValue\setOK
setOK( $ok)
Change operation status.
Definition: StatusValue.php:157
ApiBase\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
(array) When PARAM_TYPE is an array, this indicates which of the values are deprecated.
Definition: ApiBase.php:210
ApiBase\lacksSameOriginSecurity
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:572
ApiBase\PARAM_HELP_MSG_APPEND
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:139
wfDebugLog
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
Definition: GlobalFunctions.php:1007
ApiBase\getWebUITokenSalt
getWebUITokenSalt(array $params)
Fetch the salt used in the Web UI corresponding to this module.
Definition: ApiBase.php:493
ApiBase\isMain
isMain()
Returns true if this module is the main module ($this === $this->mMainModule), false otherwise.
Definition: ApiBase.php:549
ApiBase\isReadMode
isReadMode()
Indicates whether this module requires read rights.
Definition: ApiBase.php:416
ApiBase
This abstract class implements many basic API functions, and is the base of all API classes.
Definition: ApiBase.php:43
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
ApiBase\getModuleFromPath
getModuleFromPath( $path)
Get a module from its module path.
Definition: ApiBase.php:606
ApiBase\getFinalParamDescription
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2382
ApiMessage
Extension of Message implementing IApiMessage.
Definition: ApiMessage.php:26
ApiBase\PARAM_SENSITIVE
const PARAM_SENSITIVE
(boolean) Is the parameter sensitive? Note 'password'-type fields are always sensitive regardless of ...
Definition: ApiBase.php:201
ApiBase\PARAM_ISMULTI_LIMIT2
const PARAM_ISMULTI_LIMIT2
(integer) Maximum number of values, for users with the apihighimits right.
Definition: ApiBase.php:223
ExtensionRegistry\getInstance
static getInstance()
Definition: ExtensionRegistry.php:127
ApiBase\getFinalSummary
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2304
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:113
ApiBase\explodeMultiValue
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition: ApiBase.php:1466
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:54
ApiBase\PARAM_MIN
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition: ApiBase.php:107
StatusValue\isGood
isGood()
Returns whether the operation completed and didn't have any error or warnings.
Definition: StatusValue.php:121
MWException
MediaWiki exception.
Definition: MWException.php:26
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:141
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1044
ApiBase\warnOrDie
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition: ApiBase.php:2040
ApiBase\getCustomPrinter
getCustomPrinter()
If the module may only be used with a certain format module, it should override this method to return...
Definition: ApiBase.php:358
ApiBase\getFinalDescription
getFinalDescription()
Get final module description, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2320
wfTransactionalTimeLimit
wfTransactionalTimeLimit()
Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit.
Definition: GlobalFunctions.php:2784
ApiBase\getModulePath
getModulePath()
Get the path to this module.
Definition: ApiBase.php:588
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2562
ApiBase\LIMIT_BIG1
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:260
ContextSource
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
Definition: ContextSource.php:29
ApiQueryTokens\getTokenTypeSalts
static getTokenTypeSalts()
Get the salts for known token types.
Definition: ApiQueryTokens.php:63
ApiBase\PARAM_MAX
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition: ApiBase.php:98
ApiBase\dieWithException
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
Definition: ApiBase.php:2027
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:765
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
ApiBase\handleParamNormalization
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1454
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
ApiBase\isDeprecated
isDeprecated()
Indicates whether this module is deprecated.
Definition: ApiBase.php:448
ApiBase\requireAtLeastOneParameter
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:958
ApiBase\$mReplicaDB
$mReplicaDB
Definition: ApiBase.php:294
MediaWiki\Block\AbstractBlock\getType
getType()
Get the type of target for this particular block.
Definition: AbstractBlock.php:432
MediaWiki\Block\AbstractBlock\isSitewide
isSitewide( $x=null)
Indicates that the block is a sitewide block.
Definition: AbstractBlock.php:213
ApiMessage\create
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
ApiBase\PARAM_EXTRA_NAMESPACES
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is 'namespace', include these as additional possible values.
Definition: ApiBase.php:194
ApiBase\ALL_DEFAULT_STRING
const ALL_DEFAULT_STRING
Definition: ApiBase.php:257
ContextSource\setContext
setContext(IContextSource $context)
Definition: ContextSource.php:55
ApiBase\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:382
ApiBase\validateUser
validateUser( $value, $encParamName)
Validate and normalize parameters of type 'user'.
Definition: ApiBase.php:1703
ApiBlockInfoTrait
trait ApiBlockInfoTrait
Definition: ApiBlockInfoTrait.php:27
ApiBase\getWatchlistValue
getWatchlistValue( $watchlist, $titleObj, $userOption=null)
Return true if we're to watch the page, false if not, null if no change.
Definition: ApiBase.php:1095
ApiBase\needsToken
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:480
ApiBase\getModulePrefix
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:532
ApiBase\$mModuleName
string $mModuleName
Definition: ApiBase.php:293
ApiBase\setWatch
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1751
ApiBase\getContinuationManager
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:684
ContextSource\msg
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition: ContextSource.php:168
ApiBase\addError
addError( $msg, $code=null, $data=null)
Add an error for this module without aborting.
Definition: ApiBase.php:1981
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:48
ApiBase\getWatchlistUser
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1766
RE_IP_BYTE
const RE_IP_BYTE
An IPv4 address is made of 4 bytes from x00 to xFF which is d0 to d255.
Definition: IP.php:27
ApiBase\requireOnlyOneParameter
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:898
ApiBase\requireMaxOneParameter
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:933
ApiBase\dieContinueUsageIf
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition: ApiBase.php:2210
ApiBase\addDeprecation
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1948
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
ApiBase\LIMIT_SML2
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:266
ApiBase\encodeParamName
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:743
ApiBase\dieReadOnly
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2115
ApiBase\GET_VALUES_FOR_HELP
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition: ApiBase.php:273
WikiPage\newFromID
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:179
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:714
ApiBase\useTransactionalTimeLimit
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1872
ApiBase\getConditionalRequestData
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:509
ApiBase\isWriteMode
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:431
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1550
ApiBase\PARAM_HELP_MSG_INFO
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition: ApiBase.php:149
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:53
ApiBase\PARAM_RANGE_ENFORCE
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE 'integer', enforce PARAM_MIN and PARAM_MAX?
Definition: ApiBase.php:125
ApiBase\PARAM_VALUE_LINKS
const PARAM_VALUE_LINKS
(string[]) When PARAM_TYPE is an array, this may be an array mapping those values to page titles whic...
Definition: ApiBase.php:156
ChangeTags\canAddTagsAccompanyingChange
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:525
ApiBase\PARAM_TEMPLATE_VARS
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:253
ApiBase\filterIDs
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition: ApiBase.php:1886
ApiBase\LIMIT_BIG2
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:262
wfReadOnlyReason
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
Definition: GlobalFunctions.php:1183
ApiQueryTokens\getToken
static getToken(User $user, MediaWiki\Session\Session $session, $salt)
Get a token from a salt.
Definition: ApiQueryTokens.php:94
ApiBase\getModuleManager
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition: ApiBase.php:345
ApiBase\isInternal
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition: ApiBase.php:458
ApiBase\getModuleSourceInfo
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2561
User\getCanonicalName
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid.
Definition: User.php:1195
ApiBase\validateLimit
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1575
$path
$path
Definition: NoLocalSettings.php:25
ApiBase\PARAM_DFLT
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:56
ApiBase\getParameter
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:881
ApiBase\dieStatus
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:2087
ApiBase\getModuleName
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:524
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:59
ApiBase\PARAM_MAX2
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right,...
Definition: ApiBase.php:104
StatusValue\hasMessage
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
Definition: StatusValue.php:261
ApiBase\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:398
ApiBase\parseMultiValue
parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues, $allSpecifier=null, $limit1=null, $limit2=null)
Return an array of values that were given in a 'a|b|c' notation, after it optionally validates them a...
Definition: ApiBase.php:1494
$help
$help
Definition: mcc.php:32
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:540
ApiBase\getParameterFromSettings
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1136
$ext
if(!is_readable( $file)) $ext
Definition: router.php:48
ApiBase\PARAM_MAX_CHARS
const PARAM_MAX_CHARS
(integer) Maximum length of a string in characters (unicode codepoints).
Definition: ApiBase.php:235
MediaWiki\Block\AbstractBlock
Definition: AbstractBlock.php:36
ApiBase\getTitleFromTitleOrPageId
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1067
User\IGNORE_USER_RIGHTS
const IGNORE_USER_RIGHTS
Definition: User.php:84
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
ApiBase\$mModulePrefix
string $mModulePrefix
Definition: ApiBase.php:293
RawMessage
Variant of the Message class.
Definition: RawMessage.php:34
ApiBase\PARAM_HELP_MSG_PER_VALUE
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition: ApiBase.php:165
ApiBase\PARAM_SUBMODULE_PARAM_PREFIX
const PARAM_SUBMODULE_PARAM_PREFIX
(string) When PARAM_TYPE is 'submodule', used to indicate the 'g' prefix added by ApiQueryGeneratorBa...
Definition: ApiBase.php:180
ApiBase\$mParamCache
array $mParamCache
Definition: ApiBase.php:298
ApiBase\PARAM_MAX_BYTES
const PARAM_MAX_BYTES
(integer) Maximum length of a string in bytes (in UTF-8 encoding).
Definition: ApiBase.php:229
ApiHelpParamValueMessage
Message subclass that prepends wikitext for API help.
Definition: ApiHelpParamValueMessage.php:33
$IP
$IP
Definition: WebStart.php:41
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:52
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:465
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
ApiBase\dieDebug
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:2222
ExternalUserNames\isExternal
static isExternal( $username)
Tells whether the username is external or not.
Definition: ExternalUserNames.php:137
ApiBase\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
ApiBase\errorArrayToStatus
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition: ApiBase.php:1826
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter.
Definition: ApiBase.php:658
ApiBase\requirePostedParameters
requirePostedParameters( $params, $prefix='prefix')
Die if any of the specified parameters were found in the query part of the URL rather than the post b...
Definition: ApiBase.php:985
ApiBase\LIMIT_SML1
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:264
ApiBase\getHelpFlags
getHelpFlags()
Generates the list of flags for the help screen and for action=paraminfo.
Definition: ApiBase.php:2528
$type
$type
Definition: testCompression.php:50
ApiBase\$filterIDsCache
static stdClass[][] $filterIDsCache
Cache for self::filterIDs()
Definition: ApiBase.php:279