MediaWiki  master
ApiBase.php
Go to the documentation of this file.
1 <?php
29 
42 abstract class ApiBase extends ContextSource {
43 
45 
55  public const PARAM_DFLT = 0;
56 
58  public const PARAM_ISMULTI = 1;
59 
94  public const PARAM_TYPE = 2;
95 
97  public const PARAM_MAX = 3;
98 
103  public const PARAM_MAX2 = 4;
104 
106  public const PARAM_MIN = 5;
107 
109  public const PARAM_ALLOW_DUPLICATES = 6;
110 
112  public const PARAM_DEPRECATED = 7;
113 
118  public const PARAM_REQUIRED = 8;
119 
124  public const PARAM_RANGE_ENFORCE = 9;
125 
131  public const PARAM_HELP_MSG = 10;
132 
138  public const PARAM_HELP_MSG_APPEND = 11;
139 
148  public const PARAM_HELP_MSG_INFO = 12;
149 
155  public const PARAM_VALUE_LINKS = 13;
156 
164  public const PARAM_HELP_MSG_PER_VALUE = 14;
165 
172  public const PARAM_SUBMODULE_MAP = 15;
173 
179  public const PARAM_SUBMODULE_PARAM_PREFIX = 16;
180 
187  public const PARAM_ALL = 17;
188 
193  public const PARAM_EXTRA_NAMESPACES = 18;
194 
200  public const PARAM_SENSITIVE = 19;
201 
209  public const PARAM_DEPRECATED_VALUES = 20;
210 
215  public const PARAM_ISMULTI_LIMIT1 = 21;
216 
222  public const PARAM_ISMULTI_LIMIT2 = 22;
223 
228  public const PARAM_MAX_BYTES = 23;
229 
234  public const PARAM_MAX_CHARS = 24;
235 
252  public const PARAM_TEMPLATE_VARS = 25;
253 
256  public const ALL_DEFAULT_STRING = '*';
257 
259  public const LIMIT_BIG1 = 500;
261  public const LIMIT_BIG2 = 5000;
263  public const LIMIT_SML1 = 50;
265  public const LIMIT_SML2 = 500;
266 
272  public const GET_VALUES_FOR_HELP = 1;
273 
275  private static $extensionInfo = null;
276 
278  private static $filterIDsCache = [];
279 
281  private static $blockMsgMap = [
282  'blockedtext' => [ 'apierror-blocked', 'blocked' ],
283  'blockedtext-partial' => [ 'apierror-blocked-partial', 'blocked' ],
284  'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
285  'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
286  'blockedtext-composite' => [ 'apierror-blocked', 'blocked' ],
287  ];
288 
290  private $mMainModule;
293  private $mReplicaDB = null;
294  private $mParamCache = [];
296  private $mModuleSource = false;
297 
303  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
304  $this->mMainModule = $mainModule;
305  $this->mModuleName = $moduleName;
306  $this->mModulePrefix = $modulePrefix;
307 
308  if ( !$this->isMain() ) {
309  $this->setContext( $mainModule->getContext() );
310  }
311  }
312 
313  /************************************************************************/
334  abstract public function execute();
335 
341  public function getModuleManager() {
342  return null;
343  }
344 
354  public function getCustomPrinter() {
355  return null;
356  }
357 
369  protected function getExamplesMessages() {
370  return [];
371  }
372 
378  public function getHelpUrls() {
379  return [];
380  }
381 
394  protected function getAllowedParams( /* $flags = 0 */ ) {
395  // int $flags is not declared because it causes "Strict standards"
396  // warning. Most derived classes do not implement it.
397  return [];
398  }
399 
404  public function shouldCheckMaxlag() {
405  return true;
406  }
407 
412  public function isReadMode() {
413  return true;
414  }
415 
427  public function isWriteMode() {
428  return false;
429  }
430 
435  public function mustBePosted() {
436  return $this->needsToken() !== false;
437  }
438 
444  public function isDeprecated() {
445  return false;
446  }
447 
454  public function isInternal() {
455  return false;
456  }
457 
476  public function needsToken() {
477  return false;
478  }
479 
489  protected function getWebUITokenSalt( array $params ) {
490  return null;
491  }
492 
505  public function getConditionalRequestData( $condition ) {
506  return null;
507  }
508 
511  /************************************************************************/
520  public function getModuleName() {
521  return $this->mModuleName;
522  }
523 
528  public function getModulePrefix() {
529  return $this->mModulePrefix;
530  }
531 
536  public function getMain() {
537  return $this->mMainModule;
538  }
539 
545  public function isMain() {
546  return $this === $this->mMainModule;
547  }
548 
554  public function getParent() {
555  return $this->isMain() ? null : $this->getMain();
556  }
557 
568  public function lacksSameOriginSecurity() {
569  // Main module has this method overridden
570  // Safety - avoid infinite loop:
571  if ( $this->isMain() ) {
572  self::dieDebug( __METHOD__, 'base method was called on main module.' );
573  }
574 
575  return $this->getMain()->lacksSameOriginSecurity();
576  }
577 
584  public function getModulePath() {
585  if ( $this->isMain() ) {
586  return 'main';
587  } elseif ( $this->getParent()->isMain() ) {
588  return $this->getModuleName();
589  } else {
590  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
591  }
592  }
593 
602  public function getModuleFromPath( $path ) {
603  $module = $this->getMain();
604  if ( $path === 'main' ) {
605  return $module;
606  }
607 
608  $parts = explode( '+', $path );
609  if ( count( $parts ) === 1 ) {
610  // In case the '+' was typed into URL, it resolves as a space
611  $parts = explode( ' ', $path );
612  }
613 
614  $count = count( $parts );
615  for ( $i = 0; $i < $count; $i++ ) {
616  $parent = $module;
617  $manager = $parent->getModuleManager();
618  if ( $manager === null ) {
619  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
620  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
621  }
622  $module = $manager->getModule( $parts[$i] );
623 
624  if ( $module === null ) {
625  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
626  $this->dieWithError(
627  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
628  'badmodule'
629  );
630  }
631  }
632 
633  return $module;
634  }
635 
640  public function getResult() {
641  // Main module has getResult() method overridden
642  // Safety - avoid infinite loop:
643  if ( $this->isMain() ) {
644  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
645  }
646 
647  return $this->getMain()->getResult();
648  }
649 
654  public function getErrorFormatter() {
655  // Main module has getErrorFormatter() method overridden
656  // Safety - avoid infinite loop:
657  if ( $this->isMain() ) {
658  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
659  }
660 
661  return $this->getMain()->getErrorFormatter();
662  }
663 
668  protected function getDB() {
669  if ( !isset( $this->mReplicaDB ) ) {
670  $this->mReplicaDB = wfGetDB( DB_REPLICA, 'api' );
671  }
672 
673  return $this->mReplicaDB;
674  }
675 
680  public function getContinuationManager() {
681  // Main module has getContinuationManager() method overridden
682  // Safety - avoid infinite loop:
683  if ( $this->isMain() ) {
684  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
685  }
686 
687  return $this->getMain()->getContinuationManager();
688  }
689 
694  public function setContinuationManager( ApiContinuationManager $manager = null ) {
695  // Main module has setContinuationManager() method overridden
696  // Safety - avoid infinite loop:
697  if ( $this->isMain() ) {
698  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
699  }
700 
701  $this->getMain()->setContinuationManager( $manager );
702  }
703 
710  protected function getPermissionManager(): PermissionManager {
711  return MediaWikiServices::getInstance()->getPermissionManager();
712  }
713 
716  /************************************************************************/
728  public function dynamicParameterDocumentation() {
729  return null;
730  }
731 
739  public function encodeParamName( $paramName ) {
740  if ( is_array( $paramName ) ) {
741  return array_map( function ( $name ) {
742  return $this->mModulePrefix . $name;
743  }, $paramName );
744  } else {
745  return $this->mModulePrefix . $paramName;
746  }
747  }
748 
761  public function extractRequestParams( $options = [] ) {
762  if ( is_bool( $options ) ) {
763  $options = [ 'parseLimit' => $options ];
764  }
765  $options += [
766  'parseLimit' => true,
767  'safeMode' => false,
768  ];
769 
770  $parseLimit = (bool)$options['parseLimit'];
771 
772  // Cache parameters, for performance and to avoid T26564.
773  if ( !isset( $this->mParamCache[$parseLimit] ) ) {
774  $params = $this->getFinalParams() ?: [];
775  $results = [];
776  $warned = [];
777 
778  // Process all non-templates and save templates for secondary
779  // processing.
780  $toProcess = [];
781  foreach ( $params as $paramName => $paramSettings ) {
782  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
783  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
784  } else {
785  try {
786  $results[$paramName] = $this->getParameterFromSettings(
787  $paramName, $paramSettings, $parseLimit
788  );
789  } catch ( ApiUsageException $ex ) {
790  $results[$paramName] = $ex;
791  }
792  }
793  }
794 
795  // Now process all the templates by successively replacing the
796  // placeholders with all client-supplied values.
797  // This bit duplicates JavaScript logic in
798  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
799  // If you update this, see if that needs updating too.
800  while ( $toProcess ) {
801  list( $name, $targets, $settings ) = array_shift( $toProcess );
802 
803  foreach ( $targets as $placeholder => $target ) {
804  if ( !array_key_exists( $target, $results ) ) {
805  // The target wasn't processed yet, try the next one.
806  // If all hit this case, the parameter has no expansions.
807  continue;
808  }
809  if ( !is_array( $results[$target] ) || !$results[$target] ) {
810  // The target was processed but has no (valid) values.
811  // That means it has no expansions.
812  break;
813  }
814 
815  // Expand this target in the name and all other targets,
816  // then requeue if there are more targets left or put in
817  // $results if all are done.
818  unset( $targets[$placeholder] );
819  $placeholder = '{' . $placeholder . '}';
820  // @phan-suppress-next-line PhanTypeNoAccessiblePropertiesForeach
821  foreach ( $results[$target] as $value ) {
822  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
823  // Skip values that make invalid parameter names.
824  $encTargetName = $this->encodeParamName( $target );
825  if ( !isset( $warned[$encTargetName][$value] ) ) {
826  $warned[$encTargetName][$value] = true;
827  $this->addWarning( [
828  'apiwarn-ignoring-invalid-templated-value',
829  wfEscapeWikiText( $encTargetName ),
830  wfEscapeWikiText( $value ),
831  ] );
832  }
833  continue;
834  }
835 
836  $newName = str_replace( $placeholder, $value, $name );
837  if ( !$targets ) {
838  try {
839  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
840  } catch ( ApiUsageException $ex ) {
841  $results[$newName] = $ex;
842  }
843  } else {
844  $newTargets = [];
845  foreach ( $targets as $k => $v ) {
846  $newTargets[$k] = str_replace( $placeholder, $value, $v );
847  }
848  $toProcess[] = [ $newName, $newTargets, $settings ];
849  }
850  }
851  break;
852  }
853  }
854 
855  $this->mParamCache[$parseLimit] = $results;
856  }
857 
858  $ret = $this->mParamCache[$parseLimit];
859  if ( !$options['safeMode'] ) {
860  foreach ( $ret as $v ) {
861  if ( $v instanceof ApiUsageException ) {
862  throw $v;
863  }
864  }
865  }
866 
867  return $this->mParamCache[$parseLimit];
868  }
869 
876  protected function getParameter( $paramName, $parseLimit = true ) {
877  $ret = $this->extractRequestParams( [
878  'parseLimit' => $parseLimit,
879  'safeMode' => true,
880  ] )[$paramName];
881  if ( $ret instanceof ApiUsageException ) {
882  throw $ret;
883  }
884  return $ret;
885  }
886 
893  public function requireOnlyOneParameter( $params, ...$required ) {
894  $intersection = array_intersect( array_keys( array_filter( $params,
895  [ $this, 'parameterNotEmpty' ] ) ), $required );
896 
897  if ( count( $intersection ) > 1 ) {
898  $this->dieWithError( [
899  'apierror-invalidparammix',
900  Message::listParam( array_map(
901  function ( $p ) {
902  return '<var>' . $this->encodeParamName( $p ) . '</var>';
903  },
904  array_values( $intersection )
905  ) ),
906  count( $intersection ),
907  ] );
908  } elseif ( count( $intersection ) == 0 ) {
909  $this->dieWithError( [
910  'apierror-missingparam-one-of',
911  Message::listParam( array_map(
912  function ( $p ) {
913  return '<var>' . $this->encodeParamName( $p ) . '</var>';
914  },
915  array_values( $required )
916  ) ),
917  count( $required ),
918  ], 'missingparam' );
919  }
920  }
921 
928  public function requireMaxOneParameter( $params, ...$required ) {
929  $intersection = array_intersect( array_keys( array_filter( $params,
930  [ $this, 'parameterNotEmpty' ] ) ), $required );
931 
932  if ( count( $intersection ) > 1 ) {
933  $this->dieWithError( [
934  'apierror-invalidparammix',
935  Message::listParam( array_map(
936  function ( $p ) {
937  return '<var>' . $this->encodeParamName( $p ) . '</var>';
938  },
939  array_values( $intersection )
940  ) ),
941  count( $intersection ),
942  ] );
943  }
944  }
945 
953  public function requireAtLeastOneParameter( $params, ...$required ) {
954  $intersection = array_intersect(
955  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
956  $required
957  );
958 
959  if ( count( $intersection ) == 0 ) {
960  $this->dieWithError( [
961  'apierror-missingparam-at-least-one-of',
962  Message::listParam( array_map(
963  function ( $p ) {
964  return '<var>' . $this->encodeParamName( $p ) . '</var>';
965  },
966  array_values( $required )
967  ) ),
968  count( $required ),
969  ], 'missingparam' );
970  }
971  }
972 
980  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
981  // Skip if $wgDebugAPI is set or we're in internal mode
982  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
983  return;
984  }
985 
986  $queryValues = $this->getRequest()->getQueryValuesOnly();
987  $badParams = [];
988  foreach ( $params as $param ) {
989  if ( $prefix !== 'noprefix' ) {
990  $param = $this->encodeParamName( $param );
991  }
992  if ( array_key_exists( $param, $queryValues ) ) {
993  $badParams[] = $param;
994  }
995  }
996 
997  if ( $badParams ) {
998  $this->dieWithError(
999  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
1000  );
1001  }
1002  }
1003 
1010  private function parameterNotEmpty( $x ) {
1011  return !is_null( $x ) && $x !== false;
1012  }
1013 
1025  public function getTitleOrPageId( $params, $load = false ) {
1026  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1027 
1028  $pageObj = null;
1029  if ( isset( $params['title'] ) ) {
1030  $titleObj = Title::newFromText( $params['title'] );
1031  if ( !$titleObj || $titleObj->isExternal() ) {
1032  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1033  }
1034  if ( !$titleObj->canExist() ) {
1035  $this->dieWithError( 'apierror-pagecannotexist' );
1036  }
1037  $pageObj = WikiPage::factory( $titleObj );
1038  if ( $load !== false ) {
1039  $pageObj->loadPageData( $load );
1040  }
1041  } elseif ( isset( $params['pageid'] ) ) {
1042  if ( $load === false ) {
1043  $load = 'fromdb';
1044  }
1045  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1046  if ( !$pageObj ) {
1047  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1048  }
1049  }
1050 
1051  return $pageObj;
1052  }
1053 
1062  public function getTitleFromTitleOrPageId( $params ) {
1063  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1064 
1065  $titleObj = null;
1066  if ( isset( $params['title'] ) ) {
1067  $titleObj = Title::newFromText( $params['title'] );
1068  if ( !$titleObj || $titleObj->isExternal() ) {
1069  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1070  }
1071  return $titleObj;
1072  } elseif ( isset( $params['pageid'] ) ) {
1073  $titleObj = Title::newFromID( $params['pageid'] );
1074  if ( !$titleObj ) {
1075  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1076  }
1077  }
1078 
1079  return $titleObj;
1080  }
1081 
1090  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1091  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1092 
1093  switch ( $watchlist ) {
1094  case 'watch':
1095  return true;
1096 
1097  case 'unwatch':
1098  return false;
1099 
1100  case 'preferences':
1101  # If the user is already watching, don't bother checking
1102  if ( $userWatching ) {
1103  return true;
1104  }
1105  # If no user option was passed, use watchdefault and watchcreations
1106  if ( is_null( $userOption ) ) {
1107  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1108  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1109  }
1110 
1111  # Watch the article based on the user preference
1112  return $this->getUser()->getBoolOption( $userOption );
1113 
1114  case 'nochange':
1115  return $userWatching;
1116 
1117  default:
1118  return $userWatching;
1119  }
1120  }
1121 
1131  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1132  // Some classes may decide to change parameter names
1133  $encParamName = $this->encodeParamName( $paramName );
1134 
1135  // Shorthand
1136  if ( !is_array( $paramSettings ) ) {
1137  $paramSettings = [
1138  self::PARAM_DFLT => $paramSettings,
1139  ];
1140  }
1141 
1142  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1143  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1144  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1145  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1146  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1147  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1148  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1149  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1150  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1151  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1152 
1153  // When type is not given, and no choices, the type is the same as $default
1154  if ( !isset( $type ) ) {
1155  if ( isset( $default ) ) {
1156  $type = gettype( $default );
1157  } else {
1158  $type = 'NULL'; // allow everything
1159  }
1160  }
1161 
1162  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1163  $this->getMain()->markParamsSensitive( $encParamName );
1164  }
1165 
1166  if ( $type == 'boolean' ) {
1167  if ( isset( $default ) && $default !== false ) {
1168  // Having a default value of anything other than 'false' is not allowed
1169  self::dieDebug(
1170  __METHOD__,
1171  "Boolean param $encParamName's default is set to '$default'. " .
1172  'Boolean parameters must default to false.'
1173  );
1174  }
1175 
1176  $value = $this->getMain()->getCheck( $encParamName );
1177  $provided = $value;
1178  } elseif ( $type == 'upload' ) {
1179  if ( isset( $default ) ) {
1180  // Having a default value is not allowed
1181  self::dieDebug(
1182  __METHOD__,
1183  "File upload param $encParamName's default is set to " .
1184  "'$default'. File upload parameters may not have a default." );
1185  }
1186  if ( $multi ) {
1187  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1188  }
1189  $value = $this->getMain()->getUpload( $encParamName );
1190  $provided = $value->exists();
1191  if ( !$value->exists() ) {
1192  // This will get the value without trying to normalize it
1193  // (because trying to normalize a large binary file
1194  // accidentally uploaded as a field fails spectacularly)
1195  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1196  if ( $value !== null ) {
1197  $this->dieWithError(
1198  [ 'apierror-badupload', $encParamName ],
1199  "badupload_{$encParamName}"
1200  );
1201  }
1202  }
1203  } else {
1204  $value = $this->getMain()->getVal( $encParamName, $default );
1205  $provided = $this->getMain()->getCheck( $encParamName );
1206 
1207  if ( isset( $value ) && $type == 'namespace' ) {
1208  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
1209  getValidNamespaces();
1210  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1211  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1212  ) {
1213  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1214  }
1215  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1216  // specify all namespaces irrespective of PARAM_ALL.
1217  $allowAll = true;
1218  }
1219  if ( isset( $value ) && $type == 'submodule' ) {
1220  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1221  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1222  } else {
1223  $type = $this->getModuleManager()->getNames( $paramName );
1224  }
1225  }
1226 
1227  $request = $this->getMain()->getRequest();
1228  $rawValue = $request->getRawVal( $encParamName );
1229  if ( $rawValue === null ) {
1230  $rawValue = $default;
1231  }
1232 
1233  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1234  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1235  if ( $multi ) {
1236  // This loses the potential checkTitleEncoding() transformation done by
1237  // WebRequest for $_GET. Let's call that a feature.
1238  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1239  } else {
1240  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1241  }
1242  }
1243 
1244  // Check for NFC normalization, and warn
1245  if ( $rawValue !== $value ) {
1246  $this->handleParamNormalization( $paramName, $value, $rawValue );
1247  }
1248  }
1249 
1250  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1251  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1252  self::dieDebug(
1253  __METHOD__,
1254  "For param $encParamName, PARAM_ALL collides with a possible value" );
1255  }
1256  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1257  $value = $this->parseMultiValue(
1258  $encParamName,
1259  $value,
1260  $multi,
1261  is_array( $type ) ? $type : null,
1262  $allowAll ? $allSpecifier : null,
1263  $multiLimit1,
1264  $multiLimit2
1265  );
1266  }
1267 
1268  if ( isset( $value ) ) {
1269  // More validation only when choices were not given
1270  // choices were validated in parseMultiValue()
1271  if ( !is_array( $type ) ) {
1272  switch ( $type ) {
1273  case 'NULL': // nothing to do
1274  break;
1275  case 'string':
1276  case 'text':
1277  case 'password':
1278  if ( $required && $value === '' ) {
1279  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1280  }
1281  break;
1282  case 'integer': // Force everything using intval() and optionally validate limits
1283  $min = $paramSettings[self::PARAM_MIN] ?? null;
1284  $max = $paramSettings[self::PARAM_MAX] ?? null;
1285  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1286 
1287  if ( is_array( $value ) ) {
1288  $value = array_map( 'intval', $value );
1289  if ( !is_null( $min ) || !is_null( $max ) ) {
1290  foreach ( $value as &$v ) {
1291  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1292  }
1293  }
1294  } else {
1295  $value = (int)$value;
1296  if ( !is_null( $min ) || !is_null( $max ) ) {
1297  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1298  }
1299  }
1300  break;
1301  case 'limit':
1302  // Must be a number or 'max'
1303  if ( $value !== 'max' ) {
1304  $value = (int)$value;
1305  }
1306  if ( $multi ) {
1307  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1308  }
1309  if ( !$parseLimit ) {
1310  // Don't do min/max validation and don't parse 'max'
1311  break;
1312  }
1313  if ( !isset( $paramSettings[self::PARAM_MAX] )
1314  || !isset( $paramSettings[self::PARAM_MAX2] )
1315  ) {
1316  self::dieDebug(
1317  __METHOD__,
1318  "MAX1 or MAX2 are not defined for the limit $encParamName"
1319  );
1320  }
1321  if ( $value === 'max' ) {
1322  $value = $this->getMain()->canApiHighLimits()
1323  ? $paramSettings[self::PARAM_MAX2]
1324  : $paramSettings[self::PARAM_MAX];
1325  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1326  } else {
1327  $this->validateLimit(
1328  $paramName,
1329  $value,
1330  $paramSettings[self::PARAM_MIN] ?? 0,
1331  $paramSettings[self::PARAM_MAX],
1332  $paramSettings[self::PARAM_MAX2]
1333  );
1334  }
1335  break;
1336  case 'boolean':
1337  if ( $multi ) {
1338  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1339  }
1340  break;
1341  case 'timestamp':
1342  if ( is_array( $value ) ) {
1343  foreach ( $value as $key => $val ) {
1344  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1345  }
1346  } else {
1347  $value = $this->validateTimestamp( $value, $encParamName );
1348  }
1349  break;
1350  case 'user':
1351  if ( is_array( $value ) ) {
1352  foreach ( $value as $key => $val ) {
1353  $value[$key] = $this->validateUser( $val, $encParamName );
1354  }
1355  } else {
1356  $value = $this->validateUser( $value, $encParamName );
1357  }
1358  break;
1359  case 'upload': // nothing to do
1360  break;
1361  case 'tags':
1362  // If change tagging was requested, check that the tags are valid.
1363  if ( !is_array( $value ) && !$multi ) {
1364  $value = [ $value ];
1365  }
1366  $tagsStatus = ChangeTags::canAddTagsAccompanyingChange( $value );
1367  if ( !$tagsStatus->isGood() ) {
1368  $this->dieStatus( $tagsStatus );
1369  }
1370  break;
1371  default:
1372  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1373  }
1374  }
1375 
1376  // Throw out duplicates if requested
1377  if ( !$dupes && is_array( $value ) ) {
1378  $value = array_unique( $value );
1379  }
1380 
1381  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1382  foreach ( (array)$value as $val ) {
1383  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1384  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1385  ) {
1386  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1387  $paramSettings[self::PARAM_MAX_BYTES] ] );
1388  }
1389  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1390  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1391  ) {
1392  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1393  $paramSettings[self::PARAM_MAX_CHARS] ] );
1394  }
1395  }
1396  }
1397 
1398  // Set a warning if a deprecated parameter has been passed
1399  if ( $deprecated && $provided ) {
1400  $feature = $encParamName;
1401  $m = $this;
1402  while ( !$m->isMain() ) {
1403  $p = $m->getParent();
1404  $name = $m->getModuleName();
1405  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1406  $feature = "{$param}={$name}&{$feature}";
1407  $m = $p;
1408  }
1409  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1410  }
1411 
1412  // Set a warning if a deprecated parameter value has been passed
1413  $usedDeprecatedValues = $deprecatedValues && $provided
1414  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1415  : [];
1416  if ( $usedDeprecatedValues ) {
1417  $feature = "$encParamName=";
1418  $m = $this;
1419  while ( !$m->isMain() ) {
1420  $p = $m->getParent();
1421  $name = $m->getModuleName();
1422  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1423  $feature = "{$param}={$name}&{$feature}";
1424  $m = $p;
1425  }
1426  foreach ( $usedDeprecatedValues as $v ) {
1427  $msg = $deprecatedValues[$v];
1428  if ( $msg === true ) {
1429  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1430  }
1431  $this->addDeprecation( $msg, "$feature$v" );
1432  }
1433  }
1434  } elseif ( $required ) {
1435  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1436  }
1437 
1438  return $value;
1439  }
1440 
1448  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1449  $encParamName = $this->encodeParamName( $paramName );
1450  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1451  }
1452 
1460  protected function explodeMultiValue( $value, $limit ) {
1461  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1462  $sep = "\x1f";
1463  $value = substr( $value, 1 );
1464  } else {
1465  $sep = '|';
1466  }
1467 
1468  return explode( $sep, $value, $limit );
1469  }
1470 
1488  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1489  $allSpecifier = null, $limit1 = null, $limit2 = null
1490  ) {
1491  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1492  return [];
1493  }
1494  $limit1 = $limit1 ?: self::LIMIT_SML1;
1495  $limit2 = $limit2 ?: self::LIMIT_SML2;
1496 
1497  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1498  // because it unstubs $wgUser
1499  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1500  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1501  ? $limit2
1502  : $limit1;
1503 
1504  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1505  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1506  ) {
1507  return $allowedValues;
1508  }
1509 
1510  if ( count( $valuesList ) > $sizeLimit ) {
1511  $this->dieWithError(
1512  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1513  "too-many-$valueName"
1514  );
1515  }
1516 
1517  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1518  // T35482 - Allow entries with | in them for non-multiple values
1519  if ( in_array( $value, $allowedValues, true ) ) {
1520  return $value;
1521  }
1522 
1523  $values = array_map( function ( $v ) {
1524  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1525  }, $allowedValues );
1526  $this->dieWithError( [
1527  'apierror-multival-only-one-of',
1528  $valueName,
1529  Message::listParam( $values ),
1530  count( $values ),
1531  ], "multival_$valueName" );
1532  }
1533 
1534  if ( is_array( $allowedValues ) ) {
1535  // Check for unknown values
1536  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1537  if ( count( $unknown ) ) {
1538  if ( $allowMultiple ) {
1539  $this->addWarning( [
1540  'apiwarn-unrecognizedvalues',
1541  $valueName,
1542  Message::listParam( $unknown, 'comma' ),
1543  count( $unknown ),
1544  ] );
1545  } else {
1546  $this->dieWithError(
1547  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1548  "unknown_$valueName"
1549  );
1550  }
1551  }
1552  // Now throw them out
1553  $valuesList = array_intersect( $valuesList, $allowedValues );
1554  }
1555 
1556  return $allowMultiple ? $valuesList : $valuesList[0];
1557  }
1558 
1569  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1570  $enforceLimits = false
1571  ) {
1572  if ( !is_null( $min ) && $value < $min ) {
1573  $msg = ApiMessage::create(
1574  [ 'apierror-integeroutofrange-belowminimum',
1575  $this->encodeParamName( $paramName ), $min, $value ],
1576  'integeroutofrange',
1577  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1578  );
1579  // @phan-suppress-next-line PhanTypeMismatchArgument
1580  $this->warnOrDie( $msg, $enforceLimits );
1581  $value = $min;
1582  }
1583 
1584  // Minimum is always validated, whereas maximum is checked only if not
1585  // running in internal call mode
1586  if ( $this->getMain()->isInternalMode() ) {
1587  return;
1588  }
1589 
1590  // Optimization: do not check user's bot status unless really needed -- skips db query
1591  // assumes $botMax >= $max
1592  if ( !is_null( $max ) && $value > $max ) {
1593  if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1594  if ( $value > $botMax ) {
1595  $msg = ApiMessage::create(
1596  [ 'apierror-integeroutofrange-abovebotmax',
1597  $this->encodeParamName( $paramName ), $botMax, $value ],
1598  'integeroutofrange',
1599  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1600  );
1601  // @phan-suppress-next-line PhanTypeMismatchArgument
1602  $this->warnOrDie( $msg, $enforceLimits );
1603  $value = $botMax;
1604  }
1605  } else {
1606  $msg = ApiMessage::create(
1607  [ 'apierror-integeroutofrange-abovemax',
1608  $this->encodeParamName( $paramName ), $max, $value ],
1609  'integeroutofrange',
1610  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1611  );
1612  // @phan-suppress-next-line PhanTypeMismatchArgument
1613  $this->warnOrDie( $msg, $enforceLimits );
1614  $value = $max;
1615  }
1616  }
1617  }
1618 
1625  protected function validateTimestamp( $value, $encParamName ) {
1626  // Confusing synonyms for the current time accepted by wfTimestamp()
1627  // (wfTimestamp() also accepts various non-strings and the string of 14
1628  // ASCII NUL bytes, but those can't get here)
1629  if ( !$value ) {
1630  $this->addDeprecation(
1631  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1632  'unclear-"now"-timestamp'
1633  );
1634  return wfTimestamp( TS_MW );
1635  }
1636 
1637  // Explicit synonym for the current time
1638  if ( $value === 'now' ) {
1639  return wfTimestamp( TS_MW );
1640  }
1641 
1642  $timestamp = wfTimestamp( TS_MW, $value );
1643  if ( $timestamp === false ) {
1644  $this->dieWithError(
1645  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1646  "badtimestamp_{$encParamName}"
1647  );
1648  }
1649 
1650  return $timestamp;
1651  }
1652 
1662  final public function validateToken( $token, array $params ) {
1663  $tokenType = $this->needsToken();
1665  if ( !isset( $salts[$tokenType] ) ) {
1666  throw new MWException(
1667  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1668  'without registering it'
1669  );
1670  }
1671 
1672  $tokenObj = ApiQueryTokens::getToken(
1673  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1674  );
1675  if ( $tokenObj->match( $token ) ) {
1676  return true;
1677  }
1678 
1679  $webUiSalt = $this->getWebUITokenSalt( $params );
1680  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1681  $token,
1682  $webUiSalt,
1683  $this->getRequest()
1684  ) ) {
1685  return true;
1686  }
1687 
1688  return false;
1689  }
1690 
1697  private function validateUser( $value, $encParamName ) {
1698  if ( ExternalUserNames::isExternal( $value ) && User::newFromName( $value, false ) ) {
1699  return $value;
1700  }
1701 
1702  $name = User::getCanonicalName( $value, 'valid' );
1703  if ( $name !== false ) {
1704  return $name;
1705  }
1706 
1707  if (
1708  // We allow ranges as well, for blocks.
1709  IP::isIPAddress( $value ) ||
1710  // See comment for User::isIP. We don't just call that function
1711  // here because it also returns true for things like
1712  // 300.300.300.300 that are neither valid usernames nor valid IP
1713  // addresses.
1714  preg_match(
1715  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1716  $value
1717  )
1718  ) {
1719  return IP::sanitizeIP( $value );
1720  }
1721 
1722  $this->dieWithError(
1723  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1724  "baduser_{$encParamName}"
1725  );
1726  }
1727 
1730  /************************************************************************/
1741  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1742  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1743  if ( $value === null ) {
1744  return;
1745  }
1746 
1747  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1748  }
1749 
1756  public function getWatchlistUser( $params ) {
1757  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1758  $user = User::newFromName( $params['owner'], false );
1759  if ( !( $user && $user->getId() ) ) {
1760  $this->dieWithError(
1761  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1762  );
1763  }
1764  $token = $user->getOption( 'watchlisttoken' );
1765  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1766  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1767  }
1768  } else {
1769  if ( !$this->getUser()->isLoggedIn() ) {
1770  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1771  }
1772  $this->checkUserRightsAny( 'viewmywatchlist' );
1773  $user = $this->getUser();
1774  }
1775 
1776  return $user;
1777  }
1778 
1791  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1792  if ( is_string( $msg ) ) {
1793  $msg = wfMessage( $msg );
1794  } elseif ( is_array( $msg ) ) {
1795  $msg = wfMessage( ...$msg );
1796  }
1797  if ( !$msg instanceof Message ) {
1798  return null;
1799  }
1800 
1801  $msg->setContext( $context );
1802  if ( $params ) {
1803  $msg->params( $params );
1804  }
1805 
1806  return $msg;
1807  }
1808 
1816  public function errorArrayToStatus( array $errors, User $user = null ) {
1817  if ( $user === null ) {
1818  $user = $this->getUser();
1819  }
1820 
1821  $status = Status::newGood();
1822  foreach ( $errors as $error ) {
1823  if ( !is_array( $error ) ) {
1824  $error = [ $error ];
1825  }
1826  if ( is_string( $error[0] ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
1827  list( $msg, $code ) = self::$blockMsgMap[$error[0]];
1828  $status->fatal( ApiMessage::create( $msg, $code,
1829  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1830  ) );
1831  } else {
1832  $status->fatal( ...$error );
1833  }
1834  }
1835  return $status;
1836  }
1837 
1844  public function addBlockInfoToStatus( StatusValue $status, User $user = null ) {
1845  if ( $user === null ) {
1846  $user = $this->getUser();
1847  }
1848 
1849  foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
1850  if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
1851  $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
1852  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1853  ) );
1854  }
1855  }
1856  }
1857 
1862  protected function useTransactionalTimeLimit() {
1863  if ( $this->getRequest()->wasPosted() ) {
1865  }
1866  }
1867 
1876  protected function filterIDs( $fields, array $ids ) {
1877  $min = INF;
1878  $max = 0;
1879  foreach ( $fields as list( $table, $field ) ) {
1880  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1881  $row = self::$filterIDsCache[$table][$field];
1882  } else {
1883  $row = $this->getDB()->selectRow(
1884  $table,
1885  [
1886  'min_id' => "MIN($field)",
1887  'max_id' => "MAX($field)",
1888  ],
1889  '',
1890  __METHOD__
1891  );
1892  self::$filterIDsCache[$table][$field] = $row;
1893  }
1894  $min = min( $min, $row->min_id );
1895  $max = max( $max, $row->max_id );
1896  }
1897  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1898  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1899  && $id >= $min && $id <= $max;
1900  } );
1901  }
1902 
1905  /************************************************************************/
1924  public function addWarning( $msg, $code = null, $data = null ) {
1925  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1926  }
1927 
1938  public function addDeprecation( $msg, $feature, $data = [] ) {
1939  $data = (array)$data;
1940  if ( $feature !== null ) {
1941  $data['feature'] = $feature;
1942  $this->logFeatureUsage( $feature );
1943  }
1944  $this->addWarning( $msg, 'deprecation', $data );
1945 
1946  // No real need to deduplicate here, ApiErrorFormatter does that for
1947  // us (assuming the hook is deterministic).
1948  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1949  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1950  if ( count( $msgs ) > 1 ) {
1951  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1952  $msg = ( new RawMessage( $key ) )->params( $msgs );
1953  } else {
1954  $msg = reset( $msgs );
1955  }
1956  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1957  }
1958 
1971  public function addError( $msg, $code = null, $data = null ) {
1972  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1973  }
1974 
1984  public function addMessagesFromStatus(
1985  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1986  ) {
1987  $this->getErrorFormatter()->addMessagesFromStatus(
1988  $this->getModulePath(), $status, $types, $filter
1989  );
1990  }
1991 
2005  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
2006  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
2007  }
2008 
2017  public function dieWithException( $exception, array $options = [] ) {
2018  $this->dieWithError(
2019  // @phan-suppress-next-line PhanTypeMismatchArgument
2020  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
2021  );
2022  }
2023 
2030  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2031  if ( $enforceLimits ) {
2032  $this->dieWithError( $msg );
2033  } else {
2034  $this->addWarning( $msg );
2035  }
2036  }
2037 
2046  public function dieBlocked( AbstractBlock $block ) {
2047  // Die using the appropriate message depending on block type
2048  if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
2049  $this->dieWithError(
2050  'apierror-autoblocked',
2051  'autoblocked',
2052  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2053  );
2054  } elseif ( !$block->isSitewide() ) {
2055  $this->dieWithError(
2056  'apierror-blocked-partial',
2057  'blocked',
2058  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2059  );
2060  } else {
2061  $this->dieWithError(
2062  'apierror-blocked',
2063  'blocked',
2064  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2065  );
2066  }
2067  }
2068 
2077  public function dieStatus( StatusValue $status ) {
2078  if ( $status->isGood() ) {
2079  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2080  }
2081 
2082  // ApiUsageException needs a fatal status, but this method has
2083  // historically accepted any non-good status. Convert it if necessary.
2084  $status->setOK( false );
2085  if ( !$status->getErrorsByType( 'error' ) ) {
2086  $newStatus = Status::newGood();
2087  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2088  $newStatus->fatal( $err['message'], ...$err['params'] );
2089  }
2090  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2091  $newStatus->fatal( 'unknownerror-nocode' );
2092  }
2093  $status = $newStatus;
2094  }
2095 
2096  $this->addBlockInfoToStatus( $status );
2097  throw new ApiUsageException( $this, $status );
2098  }
2099 
2105  public function dieReadOnly() {
2106  $this->dieWithError(
2107  'apierror-readonly',
2108  'readonly',
2109  [ 'readonlyreason' => wfReadOnlyReason() ]
2110  );
2111  }
2112 
2121  public function checkUserRightsAny( $rights, $user = null ) {
2122  if ( !$user ) {
2123  $user = $this->getUser();
2124  }
2125  $rights = (array)$rights;
2126  if ( !$this->getPermissionManager()
2127  ->userHasAnyRight( $user, ...$rights )
2128  ) {
2129  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2130  }
2131  }
2132 
2147  public function checkTitleUserPermissions( LinkTarget $linkTarget, $actions, $options = [] ) {
2148  if ( !is_array( $options ) ) {
2149  wfDeprecated( '$user as the third parameter to ' . __METHOD__, '1.33' );
2150  $options = [ 'user' => $options ];
2151  }
2152  $user = $options['user'] ?? $this->getUser();
2153 
2154  $errors = [];
2155  foreach ( (array)$actions as $action ) {
2156  $errors = array_merge(
2157  $errors,
2158  $this->getPermissionManager()->getPermissionErrors( $action, $user, $linkTarget )
2159  );
2160  }
2161 
2162  if ( $errors ) {
2163  if ( !empty( $options['autoblock'] ) ) {
2164  $user->spreadAnyEditBlock();
2165  }
2166 
2167  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2168  }
2169  }
2170 
2182  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2183  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2184  $this->dieWithError( $msg, $code, $data, $httpCode );
2185  } else {
2186  $this->addWarning( $msg, $code, $data );
2187  }
2188  }
2189 
2199  protected function dieContinueUsageIf( $condition ) {
2200  if ( $condition ) {
2201  $this->dieWithError( 'apierror-badcontinue' );
2202  }
2203  }
2204 
2211  protected static function dieDebug( $method, $message ) {
2212  throw new MWException( "Internal error in $method: $message" );
2213  }
2214 
2221  public function logFeatureUsage( $feature ) {
2222  static $loggedFeatures = [];
2223 
2224  // Only log each feature once per request. We can get multiple calls from calls to
2225  // extractRequestParams() with different values for 'parseLimit', for example.
2226  if ( isset( $loggedFeatures[$feature] ) ) {
2227  return;
2228  }
2229  $loggedFeatures[$feature] = true;
2230 
2231  $request = $this->getRequest();
2232  $ctx = [
2233  'feature' => $feature,
2234  // Spaces to underscores in 'username' for historical reasons.
2235  'username' => str_replace( ' ', '_', $this->getUser()->getName() ),
2236  'ip' => $request->getIP(),
2237  'referer' => (string)$request->getHeader( 'Referer' ),
2238  'agent' => $this->getMain()->getUserAgent(),
2239  ];
2240 
2241  // Text string is deprecated. Remove (or replace with just $feature) in MW 1.34.
2242  $s = '"' . addslashes( $ctx['feature'] ) . '"' .
2243  ' "' . wfUrlencode( $ctx['username'] ) . '"' .
2244  ' "' . $ctx['ip'] . '"' .
2245  ' "' . addslashes( $ctx['referer'] ) . '"' .
2246  ' "' . addslashes( $ctx['agent'] ) . '"';
2247 
2248  wfDebugLog( 'api-feature-usage', $s, 'private', $ctx );
2249  }
2250 
2253  /************************************************************************/
2267  protected function getSummaryMessage() {
2268  return "apihelp-{$this->getModulePath()}-summary";
2269  }
2270 
2280  protected function getExtendedDescription() {
2281  return [ [
2282  "apihelp-{$this->getModulePath()}-extended-description",
2283  'api-help-no-extended-description',
2284  ] ];
2285  }
2286 
2293  public function getFinalSummary() {
2294  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2295  $this->getModulePrefix(),
2296  $this->getModuleName(),
2297  $this->getModulePath(),
2298  ] );
2299  return $msg;
2300  }
2301 
2309  public function getFinalDescription() {
2310  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2311  $this->getModulePrefix(),
2312  $this->getModuleName(),
2313  $this->getModulePath(),
2314  ] );
2315  $extendedDescription = self::makeMessage(
2316  $this->getExtendedDescription(), $this->getContext(), [
2317  $this->getModulePrefix(),
2318  $this->getModuleName(),
2319  $this->getModulePath(),
2320  ]
2321  );
2322 
2323  $msgs = [ $summary, $extendedDescription ];
2324 
2325  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2326 
2327  return $msgs;
2328  }
2329 
2338  public function getFinalParams( $flags = 0 ) {
2339  // @phan-suppress-next-line PhanParamTooMany
2340  $params = $this->getAllowedParams( $flags );
2341  if ( !$params ) {
2342  $params = [];
2343  }
2344 
2345  if ( $this->needsToken() ) {
2346  $params['token'] = [
2347  self::PARAM_TYPE => 'string',
2348  self::PARAM_REQUIRED => true,
2349  self::PARAM_SENSITIVE => true,
2350  self::PARAM_HELP_MSG => [
2351  'api-help-param-token',
2352  $this->needsToken(),
2353  ],
2354  ] + ( $params['token'] ?? [] );
2355  }
2356 
2357  // Avoid PHP 7.1 warning of passing $this by reference
2358  $apiModule = $this;
2359  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2360 
2361  return $params;
2362  }
2363 
2371  public function getFinalParamDescription() {
2372  $prefix = $this->getModulePrefix();
2373  $name = $this->getModuleName();
2374  $path = $this->getModulePath();
2375 
2376  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2377  $msgs = [];
2378  foreach ( $params as $param => $settings ) {
2379  if ( !is_array( $settings ) ) {
2380  $settings = [];
2381  }
2382 
2383  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2384  $msg = $settings[self::PARAM_HELP_MSG];
2385  } else {
2386  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2387  }
2388  $msg = self::makeMessage( $msg, $this->getContext(),
2389  [ $prefix, $param, $name, $path ] );
2390  if ( !$msg ) {
2391  self::dieDebug( __METHOD__,
2392  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2393  }
2394  $msgs[$param] = [ $msg ];
2395 
2396  if ( isset( $settings[self::PARAM_TYPE] ) &&
2397  $settings[self::PARAM_TYPE] === 'submodule'
2398  ) {
2399  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2400  $map = $settings[self::PARAM_SUBMODULE_MAP];
2401  } else {
2402  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2403  $map = [];
2404  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2405  $map[$submoduleName] = $prefix . $submoduleName;
2406  }
2407  }
2408 
2409  $submodules = [];
2410  $submoduleFlags = []; // for sorting: higher flags are sorted later
2411  $submoduleNames = []; // for sorting: lexicographical, ascending
2412  foreach ( $map as $v => $m ) {
2413  $isDeprecated = false;
2414  $isInternal = false;
2415  $summary = null;
2416  try {
2417  $submod = $this->getModuleFromPath( $m );
2418  if ( $submod ) {
2419  $summary = $submod->getFinalSummary();
2420  $isDeprecated = $submod->isDeprecated();
2421  $isInternal = $submod->isInternal();
2422  }
2423  } catch ( ApiUsageException $ex ) {
2424  // Ignore
2425  }
2426  if ( $summary ) {
2427  $key = $summary->getKey();
2428  $params = $summary->getParams();
2429  } else {
2430  $key = 'api-help-undocumented-module';
2431  $params = [ $m ];
2432  }
2433  $m = new ApiHelpParamValueMessage(
2434  "[[Special:ApiHelp/$m|$v]]",
2435  $key,
2436  $params,
2437  $isDeprecated,
2438  $isInternal
2439  );
2440  $submodules[] = $m->setContext( $this->getContext() );
2441  $submoduleFlags[] = ( $isDeprecated ? 1 : 0 ) | ( $isInternal ? 2 : 0 );
2442  $submoduleNames[] = $v;
2443  }
2444  // sort $submodules by $submoduleFlags and $submoduleNames
2445  array_multisort( $submoduleFlags, $submoduleNames, $submodules );
2446  $msgs[$param] = array_merge( $msgs[$param], $submodules );
2447  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2448  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2449  self::dieDebug( __METHOD__,
2450  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2451  }
2452  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2453  self::dieDebug( __METHOD__,
2454  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2455  'ApiBase::PARAM_TYPE is an array' );
2456  }
2457 
2458  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2459  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2460 
2461  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2462  if ( isset( $valueMsgs[$value] ) ) {
2463  $msg = $valueMsgs[$value];
2464  } else {
2465  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2466  }
2467  $m = self::makeMessage( $msg, $this->getContext(),
2468  [ $prefix, $param, $name, $path, $value ] );
2469  if ( $m ) {
2470  $m = new ApiHelpParamValueMessage(
2471  $value,
2472  // @phan-suppress-next-line PhanTypeMismatchArgument
2473  [ $m->getKey(), 'api-help-param-no-description' ],
2474  $m->getParams(),
2475  isset( $deprecatedValues[$value] )
2476  );
2477  $msgs[$param][] = $m->setContext( $this->getContext() );
2478  } else {
2479  self::dieDebug( __METHOD__,
2480  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2481  }
2482  }
2483  }
2484 
2485  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2486  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2487  self::dieDebug( __METHOD__,
2488  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2489  }
2490  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2491  $m = self::makeMessage( $m, $this->getContext(),
2492  [ $prefix, $param, $name, $path ] );
2493  if ( $m ) {
2494  $msgs[$param][] = $m;
2495  } else {
2496  self::dieDebug( __METHOD__,
2497  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2498  }
2499  }
2500  }
2501  }
2502 
2503  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2504 
2505  return $msgs;
2506  }
2507 
2517  protected function getHelpFlags() {
2518  $flags = [];
2519 
2520  if ( $this->isDeprecated() ) {
2521  $flags[] = 'deprecated';
2522  }
2523  if ( $this->isInternal() ) {
2524  $flags[] = 'internal';
2525  }
2526  if ( $this->isReadMode() ) {
2527  $flags[] = 'readrights';
2528  }
2529  if ( $this->isWriteMode() ) {
2530  $flags[] = 'writerights';
2531  }
2532  if ( $this->mustBePosted() ) {
2533  $flags[] = 'mustbeposted';
2534  }
2535 
2536  return $flags;
2537  }
2538 
2550  protected function getModuleSourceInfo() {
2551  global $IP;
2552 
2553  if ( $this->mModuleSource !== false ) {
2554  return $this->mModuleSource;
2555  }
2556 
2557  // First, try to find where the module comes from...
2558  $rClass = new ReflectionClass( $this );
2559  $path = $rClass->getFileName();
2560  if ( !$path ) {
2561  // No path known?
2562  $this->mModuleSource = null;
2563  return null;
2564  }
2565  $path = realpath( $path ) ?: $path;
2566 
2567  // Build map of extension directories to extension info
2568  if ( self::$extensionInfo === null ) {
2569  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2570  self::$extensionInfo = [
2571  realpath( __DIR__ ) ?: __DIR__ => [
2572  'path' => $IP,
2573  'name' => 'MediaWiki',
2574  'license-name' => 'GPL-2.0-or-later',
2575  ],
2576  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2577  realpath( $extDir ) ?: $extDir => null,
2578  ];
2579  $keep = [
2580  'path' => null,
2581  'name' => null,
2582  'namemsg' => null,
2583  'license-name' => null,
2584  ];
2585  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2586  foreach ( $group as $ext ) {
2587  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2588  // This shouldn't happen, but does anyway.
2589  continue;
2590  }
2591 
2592  $extpath = $ext['path'];
2593  if ( !is_dir( $extpath ) ) {
2594  $extpath = dirname( $extpath );
2595  }
2596  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2597  array_intersect_key( $ext, $keep );
2598  }
2599  }
2600  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2601  $extpath = $ext['path'];
2602  if ( !is_dir( $extpath ) ) {
2603  $extpath = dirname( $extpath );
2604  }
2605  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2606  array_intersect_key( $ext, $keep );
2607  }
2608  }
2609 
2610  // Now traverse parent directories until we find a match or run out of
2611  // parents.
2612  do {
2613  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2614  // Found it!
2615  $this->mModuleSource = self::$extensionInfo[$path];
2616  return $this->mModuleSource;
2617  }
2618 
2619  $oldpath = $path;
2620  $path = dirname( $path );
2621  } while ( $path !== $oldpath );
2622 
2623  // No idea what extension this might be.
2624  $this->mModuleSource = null;
2625  return null;
2626  }
2627 
2639  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2640  }
2641 
2643 }
2644 
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:142
setContext(IContextSource $context)
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set...
Definition: ApiBase.php:1010
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1448
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1062
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:155
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2371
requireAtLeastOneParameter( $params,... $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:953
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
static isIPAddress( $ip)
Determine if a string is as valid IP address or network (CIDR prefix).
Definition: IP.php:77
getErrorFormatter()
Get the error formatter.
Definition: ApiBase.php:654
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:261
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
const PARAM_MAX_BYTES
(integer) Maximum length of a string in bytes (in UTF-8 encoding).
Definition: ApiBase.php:228
isReadMode()
Indicates whether this module requires read rights.
Definition: ApiBase.php:412
getErrorsByType( $type)
Returns a list of status messages of the given type.
getResult()
Get the result object.
Definition: ApiBase.php:640
Message subclass that prepends wikitext for API help.
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:467
static $blockMsgMap
$var array Map of web UI block messages to corresponding API messages and codes
Definition: ApiBase.php:281
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2550
$IP
Definition: WebStart.php:41
getCustomPrinter()
If the module may only be used with a certain format module, it should override this method to return...
Definition: ApiBase.php:354
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php:275
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:2077
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1938
The Message class provides methods which fulfil two basic services:
Definition: Message.php:162
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1862
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1569
getMain()
Get the main module.
Definition: ApiBase.php:536
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:55
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
dieBlocked(AbstractBlock $block)
Throw an ApiUsageException, which will (if uncaught) call the main module&#39;s error handler and die wit...
Definition: ApiBase.php:2046
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition: ApiBase.php:272
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:259
Exception used to abort API execution with an error.
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:668
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:97
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition: ApiBase.php:118
static ApiMain $mMainModule
Definition: ApiBase.php:286
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition: ApiBase.php:148
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:568
This manages continuation state.
getParent()
Get the parent of this module.
Definition: ApiBase.php:554
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2005
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:761
isGood()
Returns whether the operation completed and didn&#39;t have any error or warnings.
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
Definition: ApiBase.php:2017
$mReplicaDB
Definition: ApiBase.php:293
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1625
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:164
validateUser( $value, $encParamName)
Validate and normalize parameters of type &#39;user&#39;.
Definition: ApiBase.php:1697
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true...
Definition: ApiBase.php:187
isDeprecated()
Indicates whether this module is deprecated.
Definition: ApiBase.php:444
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:378
string $mModuleName
Definition: ApiBase.php:292
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:476
requireOnlyOneParameter( $params,... $required)
Die if none or more than one of a certain set of parameters is set and not false. ...
Definition: ApiBase.php:893
IContextSource $context
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2221
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2293
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:876
const PARAM_ISMULTI_LIMIT1
(integer) Maximum number of values, for normal users.
Definition: ApiBase.php:215
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition: ApiBase.php:1791
const PARAM_MAX_CHARS
(integer) Maximum length of a string in characters (unicode codepoints).
Definition: ApiBase.php:234
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1139
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:138
parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues, $allSpecifier=null, $limit1=null, $limit2=null)
Return an array of values that were given in a &#39;a|b|c&#39; notation, after it optionally validates them a...
Definition: ApiBase.php:1488
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2267
const PARAM_SUBMODULE_PARAM_PREFIX
(string) When PARAM_TYPE is &#39;submodule&#39;, used to indicate the &#39;g&#39; prefix added by ApiQueryGeneratorBa...
Definition: ApiBase.php:179
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness, which urlencode encodes by default.
getWatchlistValue( $watchlist, $titleObj, $userOption=null)
Return true if we&#39;re to watch the page, false if not, null if no change.
Definition: ApiBase.php:1090
const PARAM_ISMULTI_LIMIT2
(integer) Maximum number of values, for users with the apihighimits right.
Definition: ApiBase.php:222
static newWithMessage(?ApiBase $module, $msg, $code=null, $data=null, $httpCode=0)
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:139
setOK( $ok)
Change operation status.
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:91
isSitewide( $x=null)
Indicates that the block is a sitewide block.
getHelpFlags()
Generates the list of flags for the help screen and for action=paraminfo.
Definition: ApiBase.php:2517
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE &#39;integer&#39;, enforce PARAM_MIN and PARAM_MAX?
Definition: ApiBase.php:124
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
dieContinueUsageIf( $condition)
Die with the &#39;badcontinue&#39; error.
Definition: ApiBase.php:2199
getModulePath()
Get the path to this module.
Definition: ApiBase.php:584
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:680
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:694
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:265
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition: ApiBase.php:1460
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:303
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is &#39;submodule&#39;, map parameter values to submodule paths.
Definition: ApiBase.php:172
getContext()
Get the base IContextSource object.
const IGNORE_USER_RIGHTS
Definition: User.php:83
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:42
Extension of Message implementing IApiMessage.
Definition: ApiMessage.php:26
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition: ApiBase.php:454
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1131
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition: Message.php:713
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:728
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:520
$filter
$help
Definition: mcc.php:32
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right, for PARAM_TYPE &#39;limit&#39;.
Definition: ApiBase.php:103
getModuleFromPath( $path)
Get a module from its module path.
Definition: ApiBase.php:602
string $mModulePrefix
Definition: ApiBase.php:292
checkTitleUserPermissions(LinkTarget $linkTarget, $actions, $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:2147
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition: ApiBase.php:2639
getType()
Get the type of target for this particular block.
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:131
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition: ApiBase.php:1816
const PARAM_SENSITIVE
(boolean) Is the parameter sensitive? Note &#39;password&#39;-type fields are always sensitive regardless of ...
Definition: ApiBase.php:200
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:263
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:252
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition: ApiBase.php:341
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1756
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:180
static getTokenTypeSalts()
Get the salts for known token types.
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1025
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:739
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition: ApiBase.php:2030
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:435
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:505
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
addError( $msg, $code=null, $data=null)
Add an error for this module without aborting.
Definition: ApiBase.php:1971
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:2182
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition: ApiBase.php:1876
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:528
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2105
const RE_IP_BYTE
Definition: IP.php:29
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
static getToken(User $user, MediaWiki\Session\Session $session, $salt)
Get a token from a salt.
Variant of the Message class.
Definition: RawMessage.php:34
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not...
getFinalDescription()
Get final module description, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2309
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1924
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
const PARAM_DEPRECATED_VALUES
(array) When PARAM_TYPE is an array, this indicates which of the values are deprecated.
Definition: ApiBase.php:209
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:58
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks...
Definition: ApiBase.php:710
if(!is_readable( $file)) $ext
Definition: router.php:48
wfTransactionalTimeLimit()
Set PHP&#39;s time limit to the larger of php.ini or $wgTransactionalTimeLimit.
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:2211
const ALL_DEFAULT_STRING
Definition: ApiBase.php:256
$mParamCache
Definition: ApiBase.php:294
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:42
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getWebUITokenSalt(array $params)
Fetch the salt used in the Web UI corresponding to this module.
Definition: ApiBase.php:489
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is &#39;namespace&#39;, include these as additional possible values.
Definition: ApiBase.php:193
validateToken( $token, array $params)
Validate the supplied token.
Definition: ApiBase.php:1662
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1741
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
static stdClass [][] $filterIDsCache
Cache for self::filterIDs()
Definition: ApiBase.php:278
array null bool $mModuleSource
Definition: ApiBase.php:296
const DB_REPLICA
Definition: defines.php:25
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:521
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:369
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
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:980
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:106
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:515
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:928
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:404
getExtendedDescription()
Return the extended help text message.
Definition: ApiBase.php:2280
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2338
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2121
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1984
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition: ApiBase.php:109
static listParam(array $list, $type='text')
Definition: Message.php:1115
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
addBlockInfoToStatus(StatusValue $status, User $user=null)
Add block info to block messages in a Status.
Definition: ApiBase.php:1844
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
static isExternal( $username)
Tells whether the username is external or not.
isMain()
Returns true if this module is the main module ($this === $this->mMainModule), false otherwise...
Definition: ApiBase.php:545
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:427
replaceMessage( $source, $dest)
If the specified source message exists, replace it with the specified destination message...
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:394