MediaWiki  master
ApiBase.php
Go to the documentation of this file.
1 <?php
29 
42 abstract class ApiBase extends ContextSource {
43 
45 
55  const PARAM_DFLT = 0;
56 
58  const PARAM_ISMULTI = 1;
59 
94  const PARAM_TYPE = 2;
95 
97  const PARAM_MAX = 3;
98 
103  const PARAM_MAX2 = 4;
104 
106  const PARAM_MIN = 5;
107 
110 
112  const PARAM_DEPRECATED = 7;
113 
118  const PARAM_REQUIRED = 8;
119 
125 
131  const PARAM_HELP_MSG = 10;
132 
139 
149 
155  const PARAM_VALUE_LINKS = 13;
156 
165 
173 
180 
187  const PARAM_ALL = 17;
188 
194 
200  const PARAM_SENSITIVE = 19;
201 
210 
216 
223 
228  const PARAM_MAX_BYTES = 23;
229 
234  const PARAM_MAX_CHARS = 24;
235 
253 
256  const ALL_DEFAULT_STRING = '*';
257 
259  const LIMIT_BIG1 = 500;
261  const LIMIT_BIG2 = 5000;
263  const LIMIT_SML1 = 50;
265  const LIMIT_SML2 = 500;
266 
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  $required = func_get_args();
895  array_shift( $required );
896 
897  $intersection = array_intersect( array_keys( array_filter( $params,
898  [ $this, 'parameterNotEmpty' ] ) ), $required );
899 
900  if ( count( $intersection ) > 1 ) {
901  $this->dieWithError( [
902  'apierror-invalidparammix',
903  Message::listParam( array_map(
904  function ( $p ) {
905  return '<var>' . $this->encodeParamName( $p ) . '</var>';
906  },
907  array_values( $intersection )
908  ) ),
909  count( $intersection ),
910  ] );
911  } elseif ( count( $intersection ) == 0 ) {
912  $this->dieWithError( [
913  'apierror-missingparam-one-of',
914  Message::listParam( array_map(
915  function ( $p ) {
916  return '<var>' . $this->encodeParamName( $p ) . '</var>';
917  },
918  array_values( $required )
919  ) ),
920  count( $required ),
921  ], 'missingparam' );
922  }
923  }
924 
931  public function requireMaxOneParameter( $params, $required /*...*/ ) {
932  $required = func_get_args();
933  array_shift( $required );
934 
935  $intersection = array_intersect( array_keys( array_filter( $params,
936  [ $this, 'parameterNotEmpty' ] ) ), $required );
937 
938  if ( count( $intersection ) > 1 ) {
939  $this->dieWithError( [
940  'apierror-invalidparammix',
941  Message::listParam( array_map(
942  function ( $p ) {
943  return '<var>' . $this->encodeParamName( $p ) . '</var>';
944  },
945  array_values( $intersection )
946  ) ),
947  count( $intersection ),
948  ] );
949  }
950  }
951 
959  public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
960  $required = func_get_args();
961  array_shift( $required );
962 
963  $intersection = array_intersect(
964  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
965  $required
966  );
967 
968  if ( count( $intersection ) == 0 ) {
969  $this->dieWithError( [
970  'apierror-missingparam-at-least-one-of',
971  Message::listParam( array_map(
972  function ( $p ) {
973  return '<var>' . $this->encodeParamName( $p ) . '</var>';
974  },
975  array_values( $required )
976  ) ),
977  count( $required ),
978  ], 'missingparam' );
979  }
980  }
981 
989  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
990  // Skip if $wgDebugAPI is set or we're in internal mode
991  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
992  return;
993  }
994 
995  $queryValues = $this->getRequest()->getQueryValues();
996  $badParams = [];
997  foreach ( $params as $param ) {
998  if ( $prefix !== 'noprefix' ) {
999  $param = $this->encodeParamName( $param );
1000  }
1001  if ( array_key_exists( $param, $queryValues ) ) {
1002  $badParams[] = $param;
1003  }
1004  }
1005 
1006  if ( $badParams ) {
1007  $this->dieWithError(
1008  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
1009  );
1010  }
1011  }
1012 
1019  private function parameterNotEmpty( $x ) {
1020  return !is_null( $x ) && $x !== false;
1021  }
1022 
1034  public function getTitleOrPageId( $params, $load = false ) {
1035  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1036 
1037  $pageObj = null;
1038  if ( isset( $params['title'] ) ) {
1039  $titleObj = Title::newFromText( $params['title'] );
1040  if ( !$titleObj || $titleObj->isExternal() ) {
1041  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1042  }
1043  if ( !$titleObj->canExist() ) {
1044  $this->dieWithError( 'apierror-pagecannotexist' );
1045  }
1046  $pageObj = WikiPage::factory( $titleObj );
1047  if ( $load !== false ) {
1048  $pageObj->loadPageData( $load );
1049  }
1050  } elseif ( isset( $params['pageid'] ) ) {
1051  if ( $load === false ) {
1052  $load = 'fromdb';
1053  }
1054  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1055  if ( !$pageObj ) {
1056  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1057  }
1058  }
1059 
1060  return $pageObj;
1061  }
1062 
1071  public function getTitleFromTitleOrPageId( $params ) {
1072  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1073 
1074  $titleObj = null;
1075  if ( isset( $params['title'] ) ) {
1076  $titleObj = Title::newFromText( $params['title'] );
1077  if ( !$titleObj || $titleObj->isExternal() ) {
1078  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1079  }
1080  return $titleObj;
1081  } elseif ( isset( $params['pageid'] ) ) {
1082  $titleObj = Title::newFromID( $params['pageid'] );
1083  if ( !$titleObj ) {
1084  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1085  }
1086  }
1087 
1088  return $titleObj;
1089  }
1090 
1099  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1100  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1101 
1102  switch ( $watchlist ) {
1103  case 'watch':
1104  return true;
1105 
1106  case 'unwatch':
1107  return false;
1108 
1109  case 'preferences':
1110  # If the user is already watching, don't bother checking
1111  if ( $userWatching ) {
1112  return true;
1113  }
1114  # If no user option was passed, use watchdefault and watchcreations
1115  if ( is_null( $userOption ) ) {
1116  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1117  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1118  }
1119 
1120  # Watch the article based on the user preference
1121  return $this->getUser()->getBoolOption( $userOption );
1122 
1123  case 'nochange':
1124  return $userWatching;
1125 
1126  default:
1127  return $userWatching;
1128  }
1129  }
1130 
1140  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1141  // Some classes may decide to change parameter names
1142  $encParamName = $this->encodeParamName( $paramName );
1143 
1144  // Shorthand
1145  if ( !is_array( $paramSettings ) ) {
1146  $paramSettings = [
1147  self::PARAM_DFLT => $paramSettings,
1148  ];
1149  }
1150 
1151  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1152  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1153  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1154  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1155  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1156  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1157  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1158  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1159  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1160  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1161 
1162  // When type is not given, and no choices, the type is the same as $default
1163  if ( !isset( $type ) ) {
1164  if ( isset( $default ) ) {
1165  $type = gettype( $default );
1166  } else {
1167  $type = 'NULL'; // allow everything
1168  }
1169  }
1170 
1171  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1172  $this->getMain()->markParamsSensitive( $encParamName );
1173  }
1174 
1175  if ( $type == 'boolean' ) {
1176  if ( isset( $default ) && $default !== false ) {
1177  // Having a default value of anything other than 'false' is not allowed
1178  self::dieDebug(
1179  __METHOD__,
1180  "Boolean param $encParamName's default is set to '$default'. " .
1181  'Boolean parameters must default to false.'
1182  );
1183  }
1184 
1185  $value = $this->getMain()->getCheck( $encParamName );
1186  $provided = $value;
1187  } elseif ( $type == 'upload' ) {
1188  if ( isset( $default ) ) {
1189  // Having a default value is not allowed
1190  self::dieDebug(
1191  __METHOD__,
1192  "File upload param $encParamName's default is set to " .
1193  "'$default'. File upload parameters may not have a default." );
1194  }
1195  if ( $multi ) {
1196  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1197  }
1198  $value = $this->getMain()->getUpload( $encParamName );
1199  $provided = $value->exists();
1200  if ( !$value->exists() ) {
1201  // This will get the value without trying to normalize it
1202  // (because trying to normalize a large binary file
1203  // accidentally uploaded as a field fails spectacularly)
1204  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1205  if ( $value !== null ) {
1206  $this->dieWithError(
1207  [ 'apierror-badupload', $encParamName ],
1208  "badupload_{$encParamName}"
1209  );
1210  }
1211  }
1212  } else {
1213  $value = $this->getMain()->getVal( $encParamName, $default );
1214  $provided = $this->getMain()->getCheck( $encParamName );
1215 
1216  if ( isset( $value ) && $type == 'namespace' ) {
1217  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
1218  getValidNamespaces();
1219  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1220  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1221  ) {
1222  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1223  }
1224  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1225  // specify all namespaces irrespective of PARAM_ALL.
1226  $allowAll = true;
1227  }
1228  if ( isset( $value ) && $type == 'submodule' ) {
1229  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1230  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1231  } else {
1232  $type = $this->getModuleManager()->getNames( $paramName );
1233  }
1234  }
1235 
1236  $request = $this->getMain()->getRequest();
1237  $rawValue = $request->getRawVal( $encParamName );
1238  if ( $rawValue === null ) {
1239  $rawValue = $default;
1240  }
1241 
1242  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1243  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1244  if ( $multi ) {
1245  // This loses the potential checkTitleEncoding() transformation done by
1246  // WebRequest for $_GET. Let's call that a feature.
1247  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1248  } else {
1249  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1250  }
1251  }
1252 
1253  // Check for NFC normalization, and warn
1254  if ( $rawValue !== $value ) {
1255  $this->handleParamNormalization( $paramName, $value, $rawValue );
1256  }
1257  }
1258 
1259  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1260  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1261  self::dieDebug(
1262  __METHOD__,
1263  "For param $encParamName, PARAM_ALL collides with a possible value" );
1264  }
1265  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1266  $value = $this->parseMultiValue(
1267  $encParamName,
1268  $value,
1269  $multi,
1270  is_array( $type ) ? $type : null,
1271  $allowAll ? $allSpecifier : null,
1272  $multiLimit1,
1273  $multiLimit2
1274  );
1275  }
1276 
1277  if ( isset( $value ) ) {
1278  // More validation only when choices were not given
1279  // choices were validated in parseMultiValue()
1280  if ( !is_array( $type ) ) {
1281  switch ( $type ) {
1282  case 'NULL': // nothing to do
1283  break;
1284  case 'string':
1285  case 'text':
1286  case 'password':
1287  if ( $required && $value === '' ) {
1288  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1289  }
1290  break;
1291  case 'integer': // Force everything using intval() and optionally validate limits
1292  $min = $paramSettings[self::PARAM_MIN] ?? null;
1293  $max = $paramSettings[self::PARAM_MAX] ?? null;
1294  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1295 
1296  if ( is_array( $value ) ) {
1297  $value = array_map( 'intval', $value );
1298  if ( !is_null( $min ) || !is_null( $max ) ) {
1299  foreach ( $value as &$v ) {
1300  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1301  }
1302  }
1303  } else {
1304  $value = (int)$value;
1305  if ( !is_null( $min ) || !is_null( $max ) ) {
1306  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1307  }
1308  }
1309  break;
1310  case 'limit':
1311  if ( !$parseLimit ) {
1312  // Don't do any validation whatsoever
1313  break;
1314  }
1315  if ( !isset( $paramSettings[self::PARAM_MAX] )
1316  || !isset( $paramSettings[self::PARAM_MAX2] )
1317  ) {
1318  self::dieDebug(
1319  __METHOD__,
1320  "MAX1 or MAX2 are not defined for the limit $encParamName"
1321  );
1322  }
1323  if ( $multi ) {
1324  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1325  }
1326  $min = $paramSettings[self::PARAM_MIN] ?? 0;
1327  if ( $value == 'max' ) {
1328  $value = $this->getMain()->canApiHighLimits()
1329  ? $paramSettings[self::PARAM_MAX2]
1330  : $paramSettings[self::PARAM_MAX];
1331  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1332  } else {
1333  $value = (int)$value;
1334  $this->validateLimit(
1335  $paramName,
1336  $value,
1337  $min,
1338  $paramSettings[self::PARAM_MAX],
1339  $paramSettings[self::PARAM_MAX2]
1340  );
1341  }
1342  break;
1343  case 'boolean':
1344  if ( $multi ) {
1345  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1346  }
1347  break;
1348  case 'timestamp':
1349  if ( is_array( $value ) ) {
1350  foreach ( $value as $key => $val ) {
1351  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1352  }
1353  } else {
1354  $value = $this->validateTimestamp( $value, $encParamName );
1355  }
1356  break;
1357  case 'user':
1358  if ( is_array( $value ) ) {
1359  foreach ( $value as $key => $val ) {
1360  $value[$key] = $this->validateUser( $val, $encParamName );
1361  }
1362  } else {
1363  $value = $this->validateUser( $value, $encParamName );
1364  }
1365  break;
1366  case 'upload': // nothing to do
1367  break;
1368  case 'tags':
1369  // If change tagging was requested, check that the tags are valid.
1370  if ( !is_array( $value ) && !$multi ) {
1371  $value = [ $value ];
1372  }
1374  if ( !$tagsStatus->isGood() ) {
1375  $this->dieStatus( $tagsStatus );
1376  }
1377  break;
1378  default:
1379  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1380  }
1381  }
1382 
1383  // Throw out duplicates if requested
1384  if ( !$dupes && is_array( $value ) ) {
1385  $value = array_unique( $value );
1386  }
1387 
1388  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1389  foreach ( (array)$value as $val ) {
1390  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1391  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1392  ) {
1393  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1394  $paramSettings[self::PARAM_MAX_BYTES] ] );
1395  }
1396  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1397  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1398  ) {
1399  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1400  $paramSettings[self::PARAM_MAX_CHARS] ] );
1401  }
1402  }
1403  }
1404 
1405  // Set a warning if a deprecated parameter has been passed
1406  if ( $deprecated && $provided ) {
1407  $feature = $encParamName;
1408  $m = $this;
1409  while ( !$m->isMain() ) {
1410  $p = $m->getParent();
1411  $name = $m->getModuleName();
1412  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1413  $feature = "{$param}={$name}&{$feature}";
1414  $m = $p;
1415  }
1416  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1417  }
1418 
1419  // Set a warning if a deprecated parameter value has been passed
1420  $usedDeprecatedValues = $deprecatedValues && $provided
1421  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1422  : [];
1423  if ( $usedDeprecatedValues ) {
1424  $feature = "$encParamName=";
1425  $m = $this;
1426  while ( !$m->isMain() ) {
1427  $p = $m->getParent();
1428  $name = $m->getModuleName();
1429  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1430  $feature = "{$param}={$name}&{$feature}";
1431  $m = $p;
1432  }
1433  foreach ( $usedDeprecatedValues as $v ) {
1434  $msg = $deprecatedValues[$v];
1435  if ( $msg === true ) {
1436  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1437  }
1438  $this->addDeprecation( $msg, "$feature$v" );
1439  }
1440  }
1441  } elseif ( $required ) {
1442  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1443  }
1444 
1445  return $value;
1446  }
1447 
1455  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1456  $encParamName = $this->encodeParamName( $paramName );
1457  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1458  }
1459 
1467  protected function explodeMultiValue( $value, $limit ) {
1468  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1469  $sep = "\x1f";
1470  $value = substr( $value, 1 );
1471  } else {
1472  $sep = '|';
1473  }
1474 
1475  return explode( $sep, $value, $limit );
1476  }
1477 
1495  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1496  $allSpecifier = null, $limit1 = null, $limit2 = null
1497  ) {
1498  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1499  return [];
1500  }
1501  $limit1 = $limit1 ?: self::LIMIT_SML1;
1502  $limit2 = $limit2 ?: self::LIMIT_SML2;
1503 
1504  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1505  // because it unstubs $wgUser
1506  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1507  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1508  ? $limit2
1509  : $limit1;
1510 
1511  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1512  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1513  ) {
1514  return $allowedValues;
1515  }
1516 
1517  if ( count( $valuesList ) > $sizeLimit ) {
1518  $this->dieWithError(
1519  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1520  "too-many-$valueName"
1521  );
1522  }
1523 
1524  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1525  // T35482 - Allow entries with | in them for non-multiple values
1526  if ( in_array( $value, $allowedValues, true ) ) {
1527  return $value;
1528  }
1529 
1530  $values = array_map( function ( $v ) {
1531  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1532  }, $allowedValues );
1533  $this->dieWithError( [
1534  'apierror-multival-only-one-of',
1535  $valueName,
1536  Message::listParam( $values ),
1537  count( $values ),
1538  ], "multival_$valueName" );
1539  }
1540 
1541  if ( is_array( $allowedValues ) ) {
1542  // Check for unknown values
1543  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1544  if ( count( $unknown ) ) {
1545  if ( $allowMultiple ) {
1546  $this->addWarning( [
1547  'apiwarn-unrecognizedvalues',
1548  $valueName,
1549  Message::listParam( $unknown, 'comma' ),
1550  count( $unknown ),
1551  ] );
1552  } else {
1553  $this->dieWithError(
1554  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1555  "unknown_$valueName"
1556  );
1557  }
1558  }
1559  // Now throw them out
1560  $valuesList = array_intersect( $valuesList, $allowedValues );
1561  }
1562 
1563  return $allowMultiple ? $valuesList : $valuesList[0];
1564  }
1565 
1576  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1577  $enforceLimits = false
1578  ) {
1579  if ( !is_null( $min ) && $value < $min ) {
1580  $msg = ApiMessage::create(
1581  [ 'apierror-integeroutofrange-belowminimum',
1582  $this->encodeParamName( $paramName ), $min, $value ],
1583  'integeroutofrange',
1584  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1585  );
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 ( !is_null( $max ) && $value > $max ) {
1599  if ( !is_null( $botMax ) && $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  $this->warnOrDie( $msg, $enforceLimits );
1608  $value = $botMax;
1609  }
1610  } else {
1611  $msg = ApiMessage::create(
1612  [ 'apierror-integeroutofrange-abovemax',
1613  $this->encodeParamName( $paramName ), $max, $value ],
1614  'integeroutofrange',
1615  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1616  );
1617  $this->warnOrDie( $msg, $enforceLimits );
1618  $value = $max;
1619  }
1620  }
1621  }
1622 
1629  protected function validateTimestamp( $value, $encParamName ) {
1630  // Confusing synonyms for the current time accepted by wfTimestamp()
1631  // (wfTimestamp() also accepts various non-strings and the string of 14
1632  // ASCII NUL bytes, but those can't get here)
1633  if ( !$value ) {
1634  $this->addDeprecation(
1635  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1636  'unclear-"now"-timestamp'
1637  );
1638  return wfTimestamp( TS_MW );
1639  }
1640 
1641  // Explicit synonym for the current time
1642  if ( $value === 'now' ) {
1643  return wfTimestamp( TS_MW );
1644  }
1645 
1646  $timestamp = wfTimestamp( TS_MW, $value );
1647  if ( $timestamp === false ) {
1648  $this->dieWithError(
1649  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1650  "badtimestamp_{$encParamName}"
1651  );
1652  }
1653 
1654  return $timestamp;
1655  }
1656 
1666  final public function validateToken( $token, array $params ) {
1667  $tokenType = $this->needsToken();
1669  if ( !isset( $salts[$tokenType] ) ) {
1670  throw new MWException(
1671  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1672  'without registering it'
1673  );
1674  }
1675 
1676  $tokenObj = ApiQueryTokens::getToken(
1677  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1678  );
1679  if ( $tokenObj->match( $token ) ) {
1680  return true;
1681  }
1682 
1683  $webUiSalt = $this->getWebUITokenSalt( $params );
1684  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1685  $token,
1686  $webUiSalt,
1687  $this->getRequest()
1688  ) ) {
1689  return true;
1690  }
1691 
1692  return false;
1693  }
1694 
1701  private function validateUser( $value, $encParamName ) {
1703  return $value;
1704  }
1705 
1706  $name = User::getCanonicalName( $value, 'valid' );
1707  if ( $name !== false ) {
1708  return $name;
1709  }
1710 
1711  if (
1712  // We allow ranges as well, for blocks.
1713  IP::isIPAddress( $value ) ||
1714  // See comment for User::isIP. We don't just call that function
1715  // here because it also returns true for things like
1716  // 300.300.300.300 that are neither valid usernames nor valid IP
1717  // addresses.
1718  preg_match(
1719  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1720  $value
1721  )
1722  ) {
1723  return IP::sanitizeIP( $value );
1724  }
1725 
1726  $this->dieWithError(
1727  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1728  "baduser_{$encParamName}"
1729  );
1730  }
1731 
1734  /************************************************************************/
1745  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1746  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1747  if ( $value === null ) {
1748  return;
1749  }
1750 
1751  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1752  }
1753 
1760  public function getWatchlistUser( $params ) {
1761  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1762  $user = User::newFromName( $params['owner'], false );
1763  if ( !( $user && $user->getId() ) ) {
1764  $this->dieWithError(
1765  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1766  );
1767  }
1768  $token = $user->getOption( 'watchlisttoken' );
1769  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1770  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1771  }
1772  } else {
1773  if ( !$this->getUser()->isLoggedIn() ) {
1774  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1775  }
1776  $this->checkUserRightsAny( 'viewmywatchlist' );
1777  $user = $this->getUser();
1778  }
1779 
1780  return $user;
1781  }
1782 
1795  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1796  if ( is_string( $msg ) ) {
1797  $msg = wfMessage( $msg );
1798  } elseif ( is_array( $msg ) ) {
1799  $msg = wfMessage( ...$msg );
1800  }
1801  if ( !$msg instanceof Message ) {
1802  return null;
1803  }
1804 
1805  $msg->setContext( $context );
1806  if ( $params ) {
1807  $msg->params( $params );
1808  }
1809 
1810  return $msg;
1811  }
1812 
1820  public function errorArrayToStatus( array $errors, User $user = null ) {
1821  if ( $user === null ) {
1822  $user = $this->getUser();
1823  }
1824 
1826  foreach ( $errors as $error ) {
1827  if ( !is_array( $error ) ) {
1828  $error = [ $error ];
1829  }
1830  if ( is_string( $error[0] ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
1831  list( $msg, $code ) = self::$blockMsgMap[$error[0]];
1832  $status->fatal( ApiMessage::create( $msg, $code,
1833  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1834  ) );
1835  } else {
1836  $status->fatal( ...$error );
1837  }
1838  }
1839  return $status;
1840  }
1841 
1849  if ( $user === null ) {
1850  $user = $this->getUser();
1851  }
1852 
1853  foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
1854  if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
1855  $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
1856  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1857  ) );
1858  }
1859  }
1860  }
1861 
1866  protected function useTransactionalTimeLimit() {
1867  if ( $this->getRequest()->wasPosted() ) {
1869  }
1870  }
1871 
1880  protected function filterIDs( $fields, array $ids ) {
1881  $min = INF;
1882  $max = 0;
1883  foreach ( $fields as list( $table, $field ) ) {
1884  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1885  $row = self::$filterIDsCache[$table][$field];
1886  } else {
1887  $row = $this->getDB()->selectRow(
1888  $table,
1889  [
1890  'min_id' => "MIN($field)",
1891  'max_id' => "MAX($field)",
1892  ],
1893  '',
1894  __METHOD__
1895  );
1896  self::$filterIDsCache[$table][$field] = $row;
1897  }
1898  $min = min( $min, $row->min_id );
1899  $max = max( $max, $row->max_id );
1900  }
1901  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1902  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1903  && $id >= $min && $id <= $max;
1904  } );
1905  }
1906 
1909  /************************************************************************/
1928  public function addWarning( $msg, $code = null, $data = null ) {
1929  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1930  }
1931 
1942  public function addDeprecation( $msg, $feature, $data = [] ) {
1943  $data = (array)$data;
1944  if ( $feature !== null ) {
1945  $data['feature'] = $feature;
1946  $this->logFeatureUsage( $feature );
1947  }
1948  $this->addWarning( $msg, 'deprecation', $data );
1949 
1950  // No real need to deduplicate here, ApiErrorFormatter does that for
1951  // us (assuming the hook is deterministic).
1952  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1953  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1954  if ( count( $msgs ) > 1 ) {
1955  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1956  $msg = ( new RawMessage( $key ) )->params( $msgs );
1957  } else {
1958  $msg = reset( $msgs );
1959  }
1960  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1961  }
1962 
1975  public function addError( $msg, $code = null, $data = null ) {
1976  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1977  }
1978 
1988  public function addMessagesFromStatus(
1989  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1990  ) {
1991  $this->getErrorFormatter()->addMessagesFromStatus(
1992  $this->getModulePath(), $status, $types, $filter
1993  );
1994  }
1995 
2009  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
2010  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
2011  }
2012 
2021  public function dieWithException( $exception, array $options = [] ) {
2022  $this->dieWithError(
2023  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
2024  );
2025  }
2026 
2033  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2034  if ( $enforceLimits ) {
2035  $this->dieWithError( $msg );
2036  } else {
2037  $this->addWarning( $msg );
2038  }
2039  }
2040 
2049  public function dieBlocked( AbstractBlock $block ) {
2050  // Die using the appropriate message depending on block type
2051  if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
2052  $this->dieWithError(
2053  'apierror-autoblocked',
2054  'autoblocked',
2055  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2056  );
2057  } elseif ( !$block->isSitewide() ) {
2058  $this->dieWithError(
2059  'apierror-blocked-partial',
2060  'blocked',
2061  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2062  );
2063  } else {
2064  $this->dieWithError(
2065  'apierror-blocked',
2066  'blocked',
2067  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2068  );
2069  }
2070  }
2071 
2080  public function dieStatus( StatusValue $status ) {
2081  if ( $status->isGood() ) {
2082  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2083  }
2084 
2085  // ApiUsageException needs a fatal status, but this method has
2086  // historically accepted any non-good status. Convert it if necessary.
2087  $status->setOK( false );
2088  if ( !$status->getErrorsByType( 'error' ) ) {
2089  $newStatus = Status::newGood();
2090  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2091  $newStatus->fatal( $err['message'], ...$err['params'] );
2092  }
2093  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2094  $newStatus->fatal( 'unknownerror-nocode' );
2095  }
2096  $status = $newStatus;
2097  }
2098 
2099  $this->addBlockInfoToStatus( $status );
2100  throw new ApiUsageException( $this, $status );
2101  }
2102 
2108  public function dieReadOnly() {
2109  $this->dieWithError(
2110  'apierror-readonly',
2111  'readonly',
2112  [ 'readonlyreason' => wfReadOnlyReason() ]
2113  );
2114  }
2115 
2124  public function checkUserRightsAny( $rights, $user = null ) {
2125  if ( !$user ) {
2126  $user = $this->getUser();
2127  }
2128  $rights = (array)$rights;
2129  if ( !$user->isAllowedAny( ...$rights ) ) {
2130  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2131  }
2132  }
2133 
2148  public function checkTitleUserPermissions( LinkTarget $linkTarget, $actions, $options = [] ) {
2149  if ( !is_array( $options ) ) {
2150  wfDeprecated( '$user as the third parameter to ' . __METHOD__, '1.33' );
2151  $options = [ 'user' => $options ];
2152  }
2153  $user = $options['user'] ?? $this->getUser();
2154 
2155  $errors = [];
2156  foreach ( (array)$actions as $action ) {
2157  $errors = array_merge(
2158  $errors,
2159  $this->getPermissionManager()->getPermissionErrors( $action, $user, $linkTarget )
2160  );
2161  }
2162 
2163  if ( $errors ) {
2164  if ( !empty( $options['autoblock'] ) ) {
2165  $user->spreadAnyEditBlock();
2166  }
2167 
2168  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2169  }
2170  }
2171 
2183  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2184  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2185  $this->dieWithError( $msg, $code, $data, $httpCode );
2186  } else {
2187  $this->addWarning( $msg, $code, $data );
2188  }
2189  }
2190 
2200  protected function dieContinueUsageIf( $condition ) {
2201  if ( $condition ) {
2202  $this->dieWithError( 'apierror-badcontinue' );
2203  }
2204  }
2205 
2212  protected static function dieDebug( $method, $message ) {
2213  throw new MWException( "Internal error in $method: $message" );
2214  }
2215 
2222  public function logFeatureUsage( $feature ) {
2223  static $loggedFeatures = [];
2224 
2225  // Only log each feature once per request. We can get multiple calls from calls to
2226  // extractRequestParams() with different values for 'parseLimit', for example.
2227  if ( isset( $loggedFeatures[$feature] ) ) {
2228  return;
2229  }
2230  $loggedFeatures[$feature] = true;
2231 
2232  $request = $this->getRequest();
2233  $ctx = [
2234  'feature' => $feature,
2235  // Spaces to underscores in 'username' for historical reasons.
2236  'username' => str_replace( ' ', '_', $this->getUser()->getName() ),
2237  'ip' => $request->getIP(),
2238  'referer' => (string)$request->getHeader( 'Referer' ),
2239  'agent' => $this->getMain()->getUserAgent(),
2240  ];
2241 
2242  // Text string is deprecated. Remove (or replace with just $feature) in MW 1.34.
2243  $s = '"' . addslashes( $ctx['feature'] ) . '"' .
2244  ' "' . wfUrlencode( $ctx['username'] ) . '"' .
2245  ' "' . $ctx['ip'] . '"' .
2246  ' "' . addslashes( $ctx['referer'] ) . '"' .
2247  ' "' . addslashes( $ctx['agent'] ) . '"';
2248 
2249  wfDebugLog( 'api-feature-usage', $s, 'private', $ctx );
2250  }
2251 
2254  /************************************************************************/
2268  protected function getSummaryMessage() {
2269  return "apihelp-{$this->getModulePath()}-summary";
2270  }
2271 
2281  protected function getExtendedDescription() {
2282  return [ [
2283  "apihelp-{$this->getModulePath()}-extended-description",
2284  'api-help-no-extended-description',
2285  ] ];
2286  }
2287 
2294  public function getFinalSummary() {
2295  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2296  $this->getModulePrefix(),
2297  $this->getModuleName(),
2298  $this->getModulePath(),
2299  ] );
2300  return $msg;
2301  }
2302 
2310  public function getFinalDescription() {
2311  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2312  $this->getModulePrefix(),
2313  $this->getModuleName(),
2314  $this->getModulePath(),
2315  ] );
2316  $extendedDescription = self::makeMessage(
2317  $this->getExtendedDescription(), $this->getContext(), [
2318  $this->getModulePrefix(),
2319  $this->getModuleName(),
2320  $this->getModulePath(),
2321  ]
2322  );
2323 
2324  $msgs = [ $summary, $extendedDescription ];
2325 
2326  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2327 
2328  return $msgs;
2329  }
2330 
2339  public function getFinalParams( $flags = 0 ) {
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  ksort( $map );
2409  $submodules = [];
2410  $deprecatedSubmodules = [];
2411  foreach ( $map as $v => $m ) {
2412  $arr = &$submodules;
2413  $isDeprecated = false;
2414  $summary = null;
2415  try {
2416  $submod = $this->getModuleFromPath( $m );
2417  if ( $submod ) {
2418  $summary = $submod->getFinalSummary();
2419  $isDeprecated = $submod->isDeprecated();
2420  if ( $isDeprecated ) {
2421  $arr = &$deprecatedSubmodules;
2422  }
2423  }
2424  } catch ( ApiUsageException $ex ) {
2425  // Ignore
2426  }
2427  if ( $summary ) {
2428  $key = $summary->getKey();
2429  $params = $summary->getParams();
2430  } else {
2431  $key = 'api-help-undocumented-module';
2432  $params = [ $m ];
2433  }
2434  $m = new ApiHelpParamValueMessage( "[[Special:ApiHelp/$m|$v]]", $key, $params, $isDeprecated );
2435  $arr[] = $m->setContext( $this->getContext() );
2436  }
2437  $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
2438  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2439  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2440  self::dieDebug( __METHOD__,
2441  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2442  }
2443  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2444  self::dieDebug( __METHOD__,
2445  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2446  'ApiBase::PARAM_TYPE is an array' );
2447  }
2448 
2449  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2450  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2451 
2452  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2453  if ( isset( $valueMsgs[$value] ) ) {
2454  $msg = $valueMsgs[$value];
2455  } else {
2456  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2457  }
2458  $m = self::makeMessage( $msg, $this->getContext(),
2459  [ $prefix, $param, $name, $path, $value ] );
2460  if ( $m ) {
2461  $m = new ApiHelpParamValueMessage(
2462  $value,
2463  [ $m->getKey(), 'api-help-param-no-description' ],
2464  $m->getParams(),
2465  isset( $deprecatedValues[$value] )
2466  );
2467  $msgs[$param][] = $m->setContext( $this->getContext() );
2468  } else {
2469  self::dieDebug( __METHOD__,
2470  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2471  }
2472  }
2473  }
2474 
2475  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2476  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2477  self::dieDebug( __METHOD__,
2478  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2479  }
2480  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2481  $m = self::makeMessage( $m, $this->getContext(),
2482  [ $prefix, $param, $name, $path ] );
2483  if ( $m ) {
2484  $msgs[$param][] = $m;
2485  } else {
2486  self::dieDebug( __METHOD__,
2487  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2488  }
2489  }
2490  }
2491  }
2492 
2493  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2494 
2495  return $msgs;
2496  }
2497 
2507  protected function getHelpFlags() {
2508  $flags = [];
2509 
2510  if ( $this->isDeprecated() ) {
2511  $flags[] = 'deprecated';
2512  }
2513  if ( $this->isInternal() ) {
2514  $flags[] = 'internal';
2515  }
2516  if ( $this->isReadMode() ) {
2517  $flags[] = 'readrights';
2518  }
2519  if ( $this->isWriteMode() ) {
2520  $flags[] = 'writerights';
2521  }
2522  if ( $this->mustBePosted() ) {
2523  $flags[] = 'mustbeposted';
2524  }
2525 
2526  return $flags;
2527  }
2528 
2540  protected function getModuleSourceInfo() {
2541  global $IP;
2542 
2543  if ( $this->mModuleSource !== false ) {
2544  return $this->mModuleSource;
2545  }
2546 
2547  // First, try to find where the module comes from...
2548  $rClass = new ReflectionClass( $this );
2549  $path = $rClass->getFileName();
2550  if ( !$path ) {
2551  // No path known?
2552  $this->mModuleSource = null;
2553  return null;
2554  }
2555  $path = realpath( $path ) ?: $path;
2556 
2557  // Build map of extension directories to extension info
2558  if ( self::$extensionInfo === null ) {
2559  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2560  self::$extensionInfo = [
2561  realpath( __DIR__ ) ?: __DIR__ => [
2562  'path' => $IP,
2563  'name' => 'MediaWiki',
2564  'license-name' => 'GPL-2.0-or-later',
2565  ],
2566  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2567  realpath( $extDir ) ?: $extDir => null,
2568  ];
2569  $keep = [
2570  'path' => null,
2571  'name' => null,
2572  'namemsg' => null,
2573  'license-name' => null,
2574  ];
2575  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2576  foreach ( $group as $ext ) {
2577  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2578  // This shouldn't happen, but does anyway.
2579  continue;
2580  }
2581 
2582  $extpath = $ext['path'];
2583  if ( !is_dir( $extpath ) ) {
2584  $extpath = dirname( $extpath );
2585  }
2586  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2587  array_intersect_key( $ext, $keep );
2588  }
2589  }
2590  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2591  $extpath = $ext['path'];
2592  if ( !is_dir( $extpath ) ) {
2593  $extpath = dirname( $extpath );
2594  }
2595  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2596  array_intersect_key( $ext, $keep );
2597  }
2598  }
2599 
2600  // Now traverse parent directories until we find a match or run out of
2601  // parents.
2602  do {
2603  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2604  // Found it!
2605  $this->mModuleSource = self::$extensionInfo[$path];
2606  return $this->mModuleSource;
2607  }
2608 
2609  $oldpath = $path;
2610  $path = dirname( $path );
2611  } while ( $path !== $oldpath );
2612 
2613  // No idea what extension this might be.
2614  $this->mModuleSource = null;
2615  return null;
2616  }
2617 
2629  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2630  }
2631 
2633 }
2634 
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:138
setContext(IContextSource $context)
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set...
Definition: ApiBase.php:1019
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1455
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1071
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
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:94
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
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
static int [][][] $filterIDsCache
Cache for self::filterIDs()
Definition: ApiBase.php:278
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
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:473
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:2540
$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
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1972
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php:275
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition: ApiBase.php:2080
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1942
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1866
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1576
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:2049
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
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
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.
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
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:2009
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:2021
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
$mReplicaDB
Definition: ApiBase.php:293
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1629
msg( $key)
Get a Message object with context set Parameters are the same as wfMessage()
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:1701
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
IContextSource $context
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2222
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2294
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:1795
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:1174
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:138
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1244
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:1495
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2268
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:1099
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
const PARAM_ISMULTI_LIMIT2
(integer) Maximum number of values, for users with the apihighimits right.
Definition: ApiBase.php:222
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:89
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:2507
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:959
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:2200
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:1467
__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
$params
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:41
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1972
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:1140
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition: Message.php:726
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:2148
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition: ApiBase.php:2629
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:767
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:931
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:1820
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:1760
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:176
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:1034
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:739
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
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:2033
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:1975
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:2183
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition: ApiBase.php:1880
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:528
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2108
const RE_IP_BYTE
Definition: IP.php:29
$parent
Definition: pageupdater.txt:71
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:2310
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1928
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:2212
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
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:271
validateToken( $token, array $params)
Validate the supplied token.
Definition: ApiBase.php:1666
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1745
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
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
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:989
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:535
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:404
getExtendedDescription()
Return the extended help text message.
Definition: ApiBase.php:2281
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2621
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2339
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2124
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1988
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
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1454
static listParam(array $list, $type='text')
Definition: Message.php:1128
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:1848
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:322
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:394