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', 'blocked' ],
284  'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
285  'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
286  ];
287 
289  private $mMainModule;
292  private $mReplicaDB = null;
293  private $mParamCache = [];
295  private $mModuleSource = false;
296 
302  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
303  $this->mMainModule = $mainModule;
304  $this->mModuleName = $moduleName;
305  $this->mModulePrefix = $modulePrefix;
306 
307  if ( !$this->isMain() ) {
308  $this->setContext( $mainModule->getContext() );
309  }
310  }
311 
312  /************************************************************************/
333  abstract public function execute();
334 
340  public function getModuleManager() {
341  return null;
342  }
343 
353  public function getCustomPrinter() {
354  return null;
355  }
356 
368  protected function getExamplesMessages() {
369  return [];
370  }
371 
377  public function getHelpUrls() {
378  return [];
379  }
380 
393  protected function getAllowedParams( /* $flags = 0 */ ) {
394  // int $flags is not declared because it causes "Strict standards"
395  // warning. Most derived classes do not implement it.
396  return [];
397  }
398 
403  public function shouldCheckMaxlag() {
404  return true;
405  }
406 
411  public function isReadMode() {
412  return true;
413  }
414 
426  public function isWriteMode() {
427  return false;
428  }
429 
434  public function mustBePosted() {
435  return $this->needsToken() !== false;
436  }
437 
443  public function isDeprecated() {
444  return false;
445  }
446 
453  public function isInternal() {
454  return false;
455  }
456 
475  public function needsToken() {
476  return false;
477  }
478 
488  protected function getWebUITokenSalt( array $params ) {
489  return null;
490  }
491 
504  public function getConditionalRequestData( $condition ) {
505  return null;
506  }
507 
510  /************************************************************************/
519  public function getModuleName() {
520  return $this->mModuleName;
521  }
522 
527  public function getModulePrefix() {
528  return $this->mModulePrefix;
529  }
530 
535  public function getMain() {
536  return $this->mMainModule;
537  }
538 
544  public function isMain() {
545  return $this === $this->mMainModule;
546  }
547 
553  public function getParent() {
554  return $this->isMain() ? null : $this->getMain();
555  }
556 
567  public function lacksSameOriginSecurity() {
568  // Main module has this method overridden
569  // Safety - avoid infinite loop:
570  if ( $this->isMain() ) {
571  self::dieDebug( __METHOD__, 'base method was called on main module.' );
572  }
573 
574  return $this->getMain()->lacksSameOriginSecurity();
575  }
576 
583  public function getModulePath() {
584  if ( $this->isMain() ) {
585  return 'main';
586  } elseif ( $this->getParent()->isMain() ) {
587  return $this->getModuleName();
588  } else {
589  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
590  }
591  }
592 
601  public function getModuleFromPath( $path ) {
602  $module = $this->getMain();
603  if ( $path === 'main' ) {
604  return $module;
605  }
606 
607  $parts = explode( '+', $path );
608  if ( count( $parts ) === 1 ) {
609  // In case the '+' was typed into URL, it resolves as a space
610  $parts = explode( ' ', $path );
611  }
612 
613  $count = count( $parts );
614  for ( $i = 0; $i < $count; $i++ ) {
615  $parent = $module;
616  $manager = $parent->getModuleManager();
617  if ( $manager === null ) {
618  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
619  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
620  }
621  $module = $manager->getModule( $parts[$i] );
622 
623  if ( $module === null ) {
624  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
625  $this->dieWithError(
626  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
627  'badmodule'
628  );
629  }
630  }
631 
632  return $module;
633  }
634 
639  public function getResult() {
640  // Main module has getResult() method overridden
641  // Safety - avoid infinite loop:
642  if ( $this->isMain() ) {
643  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
644  }
645 
646  return $this->getMain()->getResult();
647  }
648 
653  public function getErrorFormatter() {
654  // Main module has getErrorFormatter() method overridden
655  // Safety - avoid infinite loop:
656  if ( $this->isMain() ) {
657  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
658  }
659 
660  return $this->getMain()->getErrorFormatter();
661  }
662 
667  protected function getDB() {
668  if ( !isset( $this->mReplicaDB ) ) {
669  $this->mReplicaDB = wfGetDB( DB_REPLICA, 'api' );
670  }
671 
672  return $this->mReplicaDB;
673  }
674 
679  public function getContinuationManager() {
680  // Main module has getContinuationManager() method overridden
681  // Safety - avoid infinite loop:
682  if ( $this->isMain() ) {
683  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
684  }
685 
686  return $this->getMain()->getContinuationManager();
687  }
688 
693  public function setContinuationManager( ApiContinuationManager $manager = null ) {
694  // Main module has setContinuationManager() method overridden
695  // Safety - avoid infinite loop:
696  if ( $this->isMain() ) {
697  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
698  }
699 
700  $this->getMain()->setContinuationManager( $manager );
701  }
702 
709  protected function getPermissionManager(): PermissionManager {
710  return MediaWikiServices::getInstance()->getPermissionManager();
711  }
712 
715  /************************************************************************/
727  public function dynamicParameterDocumentation() {
728  return null;
729  }
730 
738  public function encodeParamName( $paramName ) {
739  if ( is_array( $paramName ) ) {
740  return array_map( function ( $name ) {
741  return $this->mModulePrefix . $name;
742  }, $paramName );
743  } else {
744  return $this->mModulePrefix . $paramName;
745  }
746  }
747 
760  public function extractRequestParams( $options = [] ) {
761  if ( is_bool( $options ) ) {
762  $options = [ 'parseLimit' => $options ];
763  }
764  $options += [
765  'parseLimit' => true,
766  'safeMode' => false,
767  ];
768 
769  $parseLimit = (bool)$options['parseLimit'];
770 
771  // Cache parameters, for performance and to avoid T26564.
772  if ( !isset( $this->mParamCache[$parseLimit] ) ) {
773  $params = $this->getFinalParams() ?: [];
774  $results = [];
775  $warned = [];
776 
777  // Process all non-templates and save templates for secondary
778  // processing.
779  $toProcess = [];
780  foreach ( $params as $paramName => $paramSettings ) {
781  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
782  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
783  } else {
784  try {
785  $results[$paramName] = $this->getParameterFromSettings(
786  $paramName, $paramSettings, $parseLimit
787  );
788  } catch ( ApiUsageException $ex ) {
789  $results[$paramName] = $ex;
790  }
791  }
792  }
793 
794  // Now process all the templates by successively replacing the
795  // placeholders with all client-supplied values.
796  // This bit duplicates JavaScript logic in
797  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
798  // If you update this, see if that needs updating too.
799  while ( $toProcess ) {
800  list( $name, $targets, $settings ) = array_shift( $toProcess );
801 
802  foreach ( $targets as $placeholder => $target ) {
803  if ( !array_key_exists( $target, $results ) ) {
804  // The target wasn't processed yet, try the next one.
805  // If all hit this case, the parameter has no expansions.
806  continue;
807  }
808  if ( !is_array( $results[$target] ) || !$results[$target] ) {
809  // The target was processed but has no (valid) values.
810  // That means it has no expansions.
811  break;
812  }
813 
814  // Expand this target in the name and all other targets,
815  // then requeue if there are more targets left or put in
816  // $results if all are done.
817  unset( $targets[$placeholder] );
818  $placeholder = '{' . $placeholder . '}';
819  // @phan-suppress-next-line PhanTypeNoAccessiblePropertiesForeach
820  foreach ( $results[$target] as $value ) {
821  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
822  // Skip values that make invalid parameter names.
823  $encTargetName = $this->encodeParamName( $target );
824  if ( !isset( $warned[$encTargetName][$value] ) ) {
825  $warned[$encTargetName][$value] = true;
826  $this->addWarning( [
827  'apiwarn-ignoring-invalid-templated-value',
828  wfEscapeWikiText( $encTargetName ),
829  wfEscapeWikiText( $value ),
830  ] );
831  }
832  continue;
833  }
834 
835  $newName = str_replace( $placeholder, $value, $name );
836  if ( !$targets ) {
837  try {
838  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
839  } catch ( ApiUsageException $ex ) {
840  $results[$newName] = $ex;
841  }
842  } else {
843  $newTargets = [];
844  foreach ( $targets as $k => $v ) {
845  $newTargets[$k] = str_replace( $placeholder, $value, $v );
846  }
847  $toProcess[] = [ $newName, $newTargets, $settings ];
848  }
849  }
850  break;
851  }
852  }
853 
854  $this->mParamCache[$parseLimit] = $results;
855  }
856 
857  $ret = $this->mParamCache[$parseLimit];
858  if ( !$options['safeMode'] ) {
859  foreach ( $ret as $v ) {
860  if ( $v instanceof ApiUsageException ) {
861  throw $v;
862  }
863  }
864  }
865 
866  return $this->mParamCache[$parseLimit];
867  }
868 
875  protected function getParameter( $paramName, $parseLimit = true ) {
876  $ret = $this->extractRequestParams( [
877  'parseLimit' => $parseLimit,
878  'safeMode' => true,
879  ] )[$paramName];
880  if ( $ret instanceof ApiUsageException ) {
881  throw $ret;
882  }
883  return $ret;
884  }
885 
892  public function requireOnlyOneParameter( $params, $required /*...*/ ) {
893  $required = func_get_args();
894  array_shift( $required );
895 
896  $intersection = array_intersect( array_keys( array_filter( $params,
897  [ $this, 'parameterNotEmpty' ] ) ), $required );
898 
899  if ( count( $intersection ) > 1 ) {
900  $this->dieWithError( [
901  'apierror-invalidparammix',
902  Message::listParam( array_map(
903  function ( $p ) {
904  return '<var>' . $this->encodeParamName( $p ) . '</var>';
905  },
906  array_values( $intersection )
907  ) ),
908  count( $intersection ),
909  ] );
910  } elseif ( count( $intersection ) == 0 ) {
911  $this->dieWithError( [
912  'apierror-missingparam-one-of',
913  Message::listParam( array_map(
914  function ( $p ) {
915  return '<var>' . $this->encodeParamName( $p ) . '</var>';
916  },
917  array_values( $required )
918  ) ),
919  count( $required ),
920  ], 'missingparam' );
921  }
922  }
923 
930  public function requireMaxOneParameter( $params, $required /*...*/ ) {
931  $required = func_get_args();
932  array_shift( $required );
933 
934  $intersection = array_intersect( array_keys( array_filter( $params,
935  [ $this, 'parameterNotEmpty' ] ) ), $required );
936 
937  if ( count( $intersection ) > 1 ) {
938  $this->dieWithError( [
939  'apierror-invalidparammix',
940  Message::listParam( array_map(
941  function ( $p ) {
942  return '<var>' . $this->encodeParamName( $p ) . '</var>';
943  },
944  array_values( $intersection )
945  ) ),
946  count( $intersection ),
947  ] );
948  }
949  }
950 
958  public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
959  $required = func_get_args();
960  array_shift( $required );
961 
962  $intersection = array_intersect(
963  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
964  $required
965  );
966 
967  if ( count( $intersection ) == 0 ) {
968  $this->dieWithError( [
969  'apierror-missingparam-at-least-one-of',
970  Message::listParam( array_map(
971  function ( $p ) {
972  return '<var>' . $this->encodeParamName( $p ) . '</var>';
973  },
974  array_values( $required )
975  ) ),
976  count( $required ),
977  ], 'missingparam' );
978  }
979  }
980 
988  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
989  // Skip if $wgDebugAPI is set or we're in internal mode
990  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
991  return;
992  }
993 
994  $queryValues = $this->getRequest()->getQueryValues();
995  $badParams = [];
996  foreach ( $params as $param ) {
997  if ( $prefix !== 'noprefix' ) {
998  $param = $this->encodeParamName( $param );
999  }
1000  if ( array_key_exists( $param, $queryValues ) ) {
1001  $badParams[] = $param;
1002  }
1003  }
1004 
1005  if ( $badParams ) {
1006  $this->dieWithError(
1007  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
1008  );
1009  }
1010  }
1011 
1018  private function parameterNotEmpty( $x ) {
1019  return !is_null( $x ) && $x !== false;
1020  }
1021 
1033  public function getTitleOrPageId( $params, $load = false ) {
1034  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1035 
1036  $pageObj = null;
1037  if ( isset( $params['title'] ) ) {
1038  $titleObj = Title::newFromText( $params['title'] );
1039  if ( !$titleObj || $titleObj->isExternal() ) {
1040  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1041  }
1042  if ( !$titleObj->canExist() ) {
1043  $this->dieWithError( 'apierror-pagecannotexist' );
1044  }
1045  $pageObj = WikiPage::factory( $titleObj );
1046  if ( $load !== false ) {
1047  $pageObj->loadPageData( $load );
1048  }
1049  } elseif ( isset( $params['pageid'] ) ) {
1050  if ( $load === false ) {
1051  $load = 'fromdb';
1052  }
1053  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1054  if ( !$pageObj ) {
1055  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1056  }
1057  }
1058 
1059  return $pageObj;
1060  }
1061 
1070  public function getTitleFromTitleOrPageId( $params ) {
1071  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1072 
1073  $titleObj = null;
1074  if ( isset( $params['title'] ) ) {
1075  $titleObj = Title::newFromText( $params['title'] );
1076  if ( !$titleObj || $titleObj->isExternal() ) {
1077  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1078  }
1079  return $titleObj;
1080  } elseif ( isset( $params['pageid'] ) ) {
1081  $titleObj = Title::newFromID( $params['pageid'] );
1082  if ( !$titleObj ) {
1083  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1084  }
1085  }
1086 
1087  return $titleObj;
1088  }
1089 
1098  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1099  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1100 
1101  switch ( $watchlist ) {
1102  case 'watch':
1103  return true;
1104 
1105  case 'unwatch':
1106  return false;
1107 
1108  case 'preferences':
1109  # If the user is already watching, don't bother checking
1110  if ( $userWatching ) {
1111  return true;
1112  }
1113  # If no user option was passed, use watchdefault and watchcreations
1114  if ( is_null( $userOption ) ) {
1115  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1116  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1117  }
1118 
1119  # Watch the article based on the user preference
1120  return $this->getUser()->getBoolOption( $userOption );
1121 
1122  case 'nochange':
1123  return $userWatching;
1124 
1125  default:
1126  return $userWatching;
1127  }
1128  }
1129 
1139  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1140  // Some classes may decide to change parameter names
1141  $encParamName = $this->encodeParamName( $paramName );
1142 
1143  // Shorthand
1144  if ( !is_array( $paramSettings ) ) {
1145  $paramSettings = [
1146  self::PARAM_DFLT => $paramSettings,
1147  ];
1148  }
1149 
1150  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1151  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1152  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1153  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1154  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1155  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1156  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1157  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1158  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1159  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1160 
1161  // When type is not given, and no choices, the type is the same as $default
1162  if ( !isset( $type ) ) {
1163  if ( isset( $default ) ) {
1164  $type = gettype( $default );
1165  } else {
1166  $type = 'NULL'; // allow everything
1167  }
1168  }
1169 
1170  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1171  $this->getMain()->markParamsSensitive( $encParamName );
1172  }
1173 
1174  if ( $type == 'boolean' ) {
1175  if ( isset( $default ) && $default !== false ) {
1176  // Having a default value of anything other than 'false' is not allowed
1177  self::dieDebug(
1178  __METHOD__,
1179  "Boolean param $encParamName's default is set to '$default'. " .
1180  'Boolean parameters must default to false.'
1181  );
1182  }
1183 
1184  $value = $this->getMain()->getCheck( $encParamName );
1185  $provided = $value;
1186  } elseif ( $type == 'upload' ) {
1187  if ( isset( $default ) ) {
1188  // Having a default value is not allowed
1189  self::dieDebug(
1190  __METHOD__,
1191  "File upload param $encParamName's default is set to " .
1192  "'$default'. File upload parameters may not have a default." );
1193  }
1194  if ( $multi ) {
1195  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1196  }
1197  $value = $this->getMain()->getUpload( $encParamName );
1198  $provided = $value->exists();
1199  if ( !$value->exists() ) {
1200  // This will get the value without trying to normalize it
1201  // (because trying to normalize a large binary file
1202  // accidentally uploaded as a field fails spectacularly)
1203  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1204  if ( $value !== null ) {
1205  $this->dieWithError(
1206  [ 'apierror-badupload', $encParamName ],
1207  "badupload_{$encParamName}"
1208  );
1209  }
1210  }
1211  } else {
1212  $value = $this->getMain()->getVal( $encParamName, $default );
1213  $provided = $this->getMain()->getCheck( $encParamName );
1214 
1215  if ( isset( $value ) && $type == 'namespace' ) {
1216  $type = MediaWikiServices::getInstance()->getNamespaceInfo()->
1217  getValidNamespaces();
1218  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1219  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1220  ) {
1221  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1222  }
1223  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1224  // specify all namespaces irrespective of PARAM_ALL.
1225  $allowAll = true;
1226  }
1227  if ( isset( $value ) && $type == 'submodule' ) {
1228  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1229  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1230  } else {
1231  $type = $this->getModuleManager()->getNames( $paramName );
1232  }
1233  }
1234 
1235  $request = $this->getMain()->getRequest();
1236  $rawValue = $request->getRawVal( $encParamName );
1237  if ( $rawValue === null ) {
1238  $rawValue = $default;
1239  }
1240 
1241  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1242  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1243  if ( $multi ) {
1244  // This loses the potential checkTitleEncoding() transformation done by
1245  // WebRequest for $_GET. Let's call that a feature.
1246  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1247  } else {
1248  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1249  }
1250  }
1251 
1252  // Check for NFC normalization, and warn
1253  if ( $rawValue !== $value ) {
1254  $this->handleParamNormalization( $paramName, $value, $rawValue );
1255  }
1256  }
1257 
1258  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1259  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1260  self::dieDebug(
1261  __METHOD__,
1262  "For param $encParamName, PARAM_ALL collides with a possible value" );
1263  }
1264  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1265  $value = $this->parseMultiValue(
1266  $encParamName,
1267  $value,
1268  $multi,
1269  is_array( $type ) ? $type : null,
1270  $allowAll ? $allSpecifier : null,
1271  $multiLimit1,
1272  $multiLimit2
1273  );
1274  }
1275 
1276  if ( isset( $value ) ) {
1277  // More validation only when choices were not given
1278  // choices were validated in parseMultiValue()
1279  if ( !is_array( $type ) ) {
1280  switch ( $type ) {
1281  case 'NULL': // nothing to do
1282  break;
1283  case 'string':
1284  case 'text':
1285  case 'password':
1286  if ( $required && $value === '' ) {
1287  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1288  }
1289  break;
1290  case 'integer': // Force everything using intval() and optionally validate limits
1291  $min = $paramSettings[self::PARAM_MIN] ?? null;
1292  $max = $paramSettings[self::PARAM_MAX] ?? null;
1293  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1294 
1295  if ( is_array( $value ) ) {
1296  $value = array_map( 'intval', $value );
1297  if ( !is_null( $min ) || !is_null( $max ) ) {
1298  foreach ( $value as &$v ) {
1299  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1300  }
1301  }
1302  } else {
1303  $value = (int)$value;
1304  if ( !is_null( $min ) || !is_null( $max ) ) {
1305  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1306  }
1307  }
1308  break;
1309  case 'limit':
1310  if ( !$parseLimit ) {
1311  // Don't do any validation whatsoever
1312  break;
1313  }
1314  if ( !isset( $paramSettings[self::PARAM_MAX] )
1315  || !isset( $paramSettings[self::PARAM_MAX2] )
1316  ) {
1317  self::dieDebug(
1318  __METHOD__,
1319  "MAX1 or MAX2 are not defined for the limit $encParamName"
1320  );
1321  }
1322  if ( $multi ) {
1323  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1324  }
1325  $min = $paramSettings[self::PARAM_MIN] ?? 0;
1326  if ( $value == 'max' ) {
1327  $value = $this->getMain()->canApiHighLimits()
1328  ? $paramSettings[self::PARAM_MAX2]
1329  : $paramSettings[self::PARAM_MAX];
1330  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1331  } else {
1332  $value = (int)$value;
1333  $this->validateLimit(
1334  $paramName,
1335  $value,
1336  $min,
1337  $paramSettings[self::PARAM_MAX],
1338  $paramSettings[self::PARAM_MAX2]
1339  );
1340  }
1341  break;
1342  case 'boolean':
1343  if ( $multi ) {
1344  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1345  }
1346  break;
1347  case 'timestamp':
1348  if ( is_array( $value ) ) {
1349  foreach ( $value as $key => $val ) {
1350  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1351  }
1352  } else {
1353  $value = $this->validateTimestamp( $value, $encParamName );
1354  }
1355  break;
1356  case 'user':
1357  if ( is_array( $value ) ) {
1358  foreach ( $value as $key => $val ) {
1359  $value[$key] = $this->validateUser( $val, $encParamName );
1360  }
1361  } else {
1362  $value = $this->validateUser( $value, $encParamName );
1363  }
1364  break;
1365  case 'upload': // nothing to do
1366  break;
1367  case 'tags':
1368  // If change tagging was requested, check that the tags are valid.
1369  if ( !is_array( $value ) && !$multi ) {
1370  $value = [ $value ];
1371  }
1373  if ( !$tagsStatus->isGood() ) {
1374  $this->dieStatus( $tagsStatus );
1375  }
1376  break;
1377  default:
1378  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1379  }
1380  }
1381 
1382  // Throw out duplicates if requested
1383  if ( !$dupes && is_array( $value ) ) {
1384  $value = array_unique( $value );
1385  }
1386 
1387  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1388  foreach ( (array)$value as $val ) {
1389  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1390  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1391  ) {
1392  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1393  $paramSettings[self::PARAM_MAX_BYTES] ] );
1394  }
1395  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1396  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1397  ) {
1398  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1399  $paramSettings[self::PARAM_MAX_CHARS] ] );
1400  }
1401  }
1402  }
1403 
1404  // Set a warning if a deprecated parameter has been passed
1405  if ( $deprecated && $provided ) {
1406  $feature = $encParamName;
1407  $m = $this;
1408  while ( !$m->isMain() ) {
1409  $p = $m->getParent();
1410  $name = $m->getModuleName();
1411  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1412  $feature = "{$param}={$name}&{$feature}";
1413  $m = $p;
1414  }
1415  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1416  }
1417 
1418  // Set a warning if a deprecated parameter value has been passed
1419  $usedDeprecatedValues = $deprecatedValues && $provided
1420  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1421  : [];
1422  if ( $usedDeprecatedValues ) {
1423  $feature = "$encParamName=";
1424  $m = $this;
1425  while ( !$m->isMain() ) {
1426  $p = $m->getParent();
1427  $name = $m->getModuleName();
1428  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1429  $feature = "{$param}={$name}&{$feature}";
1430  $m = $p;
1431  }
1432  foreach ( $usedDeprecatedValues as $v ) {
1433  $msg = $deprecatedValues[$v];
1434  if ( $msg === true ) {
1435  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1436  }
1437  $this->addDeprecation( $msg, "$feature$v" );
1438  }
1439  }
1440  } elseif ( $required ) {
1441  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1442  }
1443 
1444  return $value;
1445  }
1446 
1454  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1455  $encParamName = $this->encodeParamName( $paramName );
1456  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1457  }
1458 
1466  protected function explodeMultiValue( $value, $limit ) {
1467  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1468  $sep = "\x1f";
1469  $value = substr( $value, 1 );
1470  } else {
1471  $sep = '|';
1472  }
1473 
1474  return explode( $sep, $value, $limit );
1475  }
1476 
1494  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1495  $allSpecifier = null, $limit1 = null, $limit2 = null
1496  ) {
1497  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1498  return [];
1499  }
1500  $limit1 = $limit1 ?: self::LIMIT_SML1;
1501  $limit2 = $limit2 ?: self::LIMIT_SML2;
1502 
1503  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1504  // because it unstubs $wgUser
1505  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1506  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1507  ? $limit2
1508  : $limit1;
1509 
1510  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1511  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1512  ) {
1513  return $allowedValues;
1514  }
1515 
1516  if ( count( $valuesList ) > $sizeLimit ) {
1517  $this->dieWithError(
1518  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1519  "too-many-$valueName"
1520  );
1521  }
1522 
1523  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1524  // T35482 - Allow entries with | in them for non-multiple values
1525  if ( in_array( $value, $allowedValues, true ) ) {
1526  return $value;
1527  }
1528 
1529  $values = array_map( function ( $v ) {
1530  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1531  }, $allowedValues );
1532  $this->dieWithError( [
1533  'apierror-multival-only-one-of',
1534  $valueName,
1535  Message::listParam( $values ),
1536  count( $values ),
1537  ], "multival_$valueName" );
1538  }
1539 
1540  if ( is_array( $allowedValues ) ) {
1541  // Check for unknown values
1542  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1543  if ( count( $unknown ) ) {
1544  if ( $allowMultiple ) {
1545  $this->addWarning( [
1546  'apiwarn-unrecognizedvalues',
1547  $valueName,
1548  Message::listParam( $unknown, 'comma' ),
1549  count( $unknown ),
1550  ] );
1551  } else {
1552  $this->dieWithError(
1553  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1554  "unknown_$valueName"
1555  );
1556  }
1557  }
1558  // Now throw them out
1559  $valuesList = array_intersect( $valuesList, $allowedValues );
1560  }
1561 
1562  return $allowMultiple ? $valuesList : $valuesList[0];
1563  }
1564 
1575  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1576  $enforceLimits = false
1577  ) {
1578  if ( !is_null( $min ) && $value < $min ) {
1579  $msg = ApiMessage::create(
1580  [ 'apierror-integeroutofrange-belowminimum',
1581  $this->encodeParamName( $paramName ), $min, $value ],
1582  'integeroutofrange',
1583  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1584  );
1585  $this->warnOrDie( $msg, $enforceLimits );
1586  $value = $min;
1587  }
1588 
1589  // Minimum is always validated, whereas maximum is checked only if not
1590  // running in internal call mode
1591  if ( $this->getMain()->isInternalMode() ) {
1592  return;
1593  }
1594 
1595  // Optimization: do not check user's bot status unless really needed -- skips db query
1596  // assumes $botMax >= $max
1597  if ( !is_null( $max ) && $value > $max ) {
1598  if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1599  if ( $value > $botMax ) {
1600  $msg = ApiMessage::create(
1601  [ 'apierror-integeroutofrange-abovebotmax',
1602  $this->encodeParamName( $paramName ), $botMax, $value ],
1603  'integeroutofrange',
1604  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1605  );
1606  $this->warnOrDie( $msg, $enforceLimits );
1607  $value = $botMax;
1608  }
1609  } else {
1610  $msg = ApiMessage::create(
1611  [ 'apierror-integeroutofrange-abovemax',
1612  $this->encodeParamName( $paramName ), $max, $value ],
1613  'integeroutofrange',
1614  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1615  );
1616  $this->warnOrDie( $msg, $enforceLimits );
1617  $value = $max;
1618  }
1619  }
1620  }
1621 
1628  protected function validateTimestamp( $value, $encParamName ) {
1629  // Confusing synonyms for the current time accepted by wfTimestamp()
1630  // (wfTimestamp() also accepts various non-strings and the string of 14
1631  // ASCII NUL bytes, but those can't get here)
1632  if ( !$value ) {
1633  $this->addDeprecation(
1634  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1635  'unclear-"now"-timestamp'
1636  );
1637  return wfTimestamp( TS_MW );
1638  }
1639 
1640  // Explicit synonym for the current time
1641  if ( $value === 'now' ) {
1642  return wfTimestamp( TS_MW );
1643  }
1644 
1645  $timestamp = wfTimestamp( TS_MW, $value );
1646  if ( $timestamp === false ) {
1647  $this->dieWithError(
1648  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1649  "badtimestamp_{$encParamName}"
1650  );
1651  }
1652 
1653  return $timestamp;
1654  }
1655 
1665  final public function validateToken( $token, array $params ) {
1666  $tokenType = $this->needsToken();
1668  if ( !isset( $salts[$tokenType] ) ) {
1669  throw new MWException(
1670  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1671  'without registering it'
1672  );
1673  }
1674 
1675  $tokenObj = ApiQueryTokens::getToken(
1676  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1677  );
1678  if ( $tokenObj->match( $token ) ) {
1679  return true;
1680  }
1681 
1682  $webUiSalt = $this->getWebUITokenSalt( $params );
1683  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1684  $token,
1685  $webUiSalt,
1686  $this->getRequest()
1687  ) ) {
1688  return true;
1689  }
1690 
1691  return false;
1692  }
1693 
1700  private function validateUser( $value, $encParamName ) {
1702  return $value;
1703  }
1704 
1705  $name = User::getCanonicalName( $value, 'valid' );
1706  if ( $name !== false ) {
1707  return $name;
1708  }
1709 
1710  if (
1711  // We allow ranges as well, for blocks.
1712  IP::isIPAddress( $value ) ||
1713  // See comment for User::isIP. We don't just call that function
1714  // here because it also returns true for things like
1715  // 300.300.300.300 that are neither valid usernames nor valid IP
1716  // addresses.
1717  preg_match(
1718  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1719  $value
1720  )
1721  ) {
1722  return IP::sanitizeIP( $value );
1723  }
1724 
1725  $this->dieWithError(
1726  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1727  "baduser_{$encParamName}"
1728  );
1729  }
1730 
1733  /************************************************************************/
1744  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1745  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1746  if ( $value === null ) {
1747  return;
1748  }
1749 
1750  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1751  }
1752 
1759  public function getWatchlistUser( $params ) {
1760  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1761  $user = User::newFromName( $params['owner'], false );
1762  if ( !( $user && $user->getId() ) ) {
1763  $this->dieWithError(
1764  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1765  );
1766  }
1767  $token = $user->getOption( 'watchlisttoken' );
1768  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1769  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1770  }
1771  } else {
1772  if ( !$this->getUser()->isLoggedIn() ) {
1773  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1774  }
1775  $this->checkUserRightsAny( 'viewmywatchlist' );
1776  $user = $this->getUser();
1777  }
1778 
1779  return $user;
1780  }
1781 
1794  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1795  if ( is_string( $msg ) ) {
1796  $msg = wfMessage( $msg );
1797  } elseif ( is_array( $msg ) ) {
1798  $msg = wfMessage( ...$msg );
1799  }
1800  if ( !$msg instanceof Message ) {
1801  return null;
1802  }
1803 
1804  $msg->setContext( $context );
1805  if ( $params ) {
1806  $msg->params( $params );
1807  }
1808 
1809  return $msg;
1810  }
1811 
1819  public function errorArrayToStatus( array $errors, User $user = null ) {
1820  if ( $user === null ) {
1821  $user = $this->getUser();
1822  }
1823 
1825  foreach ( $errors as $error ) {
1826  if ( !is_array( $error ) ) {
1827  $error = [ $error ];
1828  }
1829  if ( is_string( $error[0] ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
1830  list( $msg, $code ) = self::$blockMsgMap[$error[0]];
1831  $status->fatal( ApiMessage::create( $msg, $code,
1832  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1833  ) );
1834  } else {
1835  $status->fatal( ...$error );
1836  }
1837  }
1838  return $status;
1839  }
1840 
1848  if ( $user === null ) {
1849  $user = $this->getUser();
1850  }
1851 
1852  foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
1853  if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
1854  $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
1855  [ 'blockinfo' => $this->getBlockDetails( $user->getBlock() ) ]
1856  ) );
1857  }
1858  }
1859  }
1860 
1865  protected function useTransactionalTimeLimit() {
1866  if ( $this->getRequest()->wasPosted() ) {
1868  }
1869  }
1870 
1879  protected function filterIDs( $fields, array $ids ) {
1880  $min = INF;
1881  $max = 0;
1882  foreach ( $fields as list( $table, $field ) ) {
1883  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1884  $row = self::$filterIDsCache[$table][$field];
1885  } else {
1886  $row = $this->getDB()->selectRow(
1887  $table,
1888  [
1889  'min_id' => "MIN($field)",
1890  'max_id' => "MAX($field)",
1891  ],
1892  '',
1893  __METHOD__
1894  );
1895  self::$filterIDsCache[$table][$field] = $row;
1896  }
1897  $min = min( $min, $row->min_id );
1898  $max = max( $max, $row->max_id );
1899  }
1900  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1901  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1902  && $id >= $min && $id <= $max;
1903  } );
1904  }
1905 
1908  /************************************************************************/
1927  public function addWarning( $msg, $code = null, $data = null ) {
1928  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1929  }
1930 
1941  public function addDeprecation( $msg, $feature, $data = [] ) {
1942  $data = (array)$data;
1943  if ( $feature !== null ) {
1944  $data['feature'] = $feature;
1945  $this->logFeatureUsage( $feature );
1946  }
1947  $this->addWarning( $msg, 'deprecation', $data );
1948 
1949  // No real need to deduplicate here, ApiErrorFormatter does that for
1950  // us (assuming the hook is deterministic).
1951  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1952  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1953  if ( count( $msgs ) > 1 ) {
1954  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1955  $msg = ( new RawMessage( $key ) )->params( $msgs );
1956  } else {
1957  $msg = reset( $msgs );
1958  }
1959  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1960  }
1961 
1974  public function addError( $msg, $code = null, $data = null ) {
1975  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1976  }
1977 
1987  public function addMessagesFromStatus(
1988  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1989  ) {
1990  $this->getErrorFormatter()->addMessagesFromStatus(
1991  $this->getModulePath(), $status, $types, $filter
1992  );
1993  }
1994 
2008  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
2009  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
2010  }
2011 
2020  public function dieWithException( $exception, array $options = [] ) {
2021  $this->dieWithError(
2022  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
2023  );
2024  }
2025 
2032  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2033  if ( $enforceLimits ) {
2034  $this->dieWithError( $msg );
2035  } else {
2036  $this->addWarning( $msg );
2037  }
2038  }
2039 
2048  public function dieBlocked( AbstractBlock $block ) {
2049  // Die using the appropriate message depending on block type
2050  if ( $block->getType() == DatabaseBlock::TYPE_AUTO ) {
2051  $this->dieWithError(
2052  'apierror-autoblocked',
2053  'autoblocked',
2054  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2055  );
2056  } elseif ( !$block->isSitewide() ) {
2057  $this->dieWithError(
2058  'apierror-blocked-partial',
2059  'blocked',
2060  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2061  );
2062  } else {
2063  $this->dieWithError(
2064  'apierror-blocked',
2065  'blocked',
2066  [ 'blockinfo' => $this->getBlockDetails( $block ) ]
2067  );
2068  }
2069  }
2070 
2079  public function dieStatus( StatusValue $status ) {
2080  if ( $status->isGood() ) {
2081  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2082  }
2083 
2084  // ApiUsageException needs a fatal status, but this method has
2085  // historically accepted any non-good status. Convert it if necessary.
2086  $status->setOK( false );
2087  if ( !$status->getErrorsByType( 'error' ) ) {
2088  $newStatus = Status::newGood();
2089  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2090  $newStatus->fatal( $err['message'], ...$err['params'] );
2091  }
2092  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2093  $newStatus->fatal( 'unknownerror-nocode' );
2094  }
2095  $status = $newStatus;
2096  }
2097 
2098  $this->addBlockInfoToStatus( $status );
2099  throw new ApiUsageException( $this, $status );
2100  }
2101 
2107  public function dieReadOnly() {
2108  $this->dieWithError(
2109  'apierror-readonly',
2110  'readonly',
2111  [ 'readonlyreason' => wfReadOnlyReason() ]
2112  );
2113  }
2114 
2123  public function checkUserRightsAny( $rights, $user = null ) {
2124  if ( !$user ) {
2125  $user = $this->getUser();
2126  }
2127  $rights = (array)$rights;
2128  if ( !$user->isAllowedAny( ...$rights ) ) {
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  $params = $this->getAllowedParams( $flags );
2340  if ( !$params ) {
2341  $params = [];
2342  }
2343 
2344  if ( $this->needsToken() ) {
2345  $params['token'] = [
2346  self::PARAM_TYPE => 'string',
2347  self::PARAM_REQUIRED => true,
2348  self::PARAM_SENSITIVE => true,
2349  self::PARAM_HELP_MSG => [
2350  'api-help-param-token',
2351  $this->needsToken(),
2352  ],
2353  ] + ( $params['token'] ?? [] );
2354  }
2355 
2356  // Avoid PHP 7.1 warning of passing $this by reference
2357  $apiModule = $this;
2358  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2359 
2360  return $params;
2361  }
2362 
2370  public function getFinalParamDescription() {
2371  $prefix = $this->getModulePrefix();
2372  $name = $this->getModuleName();
2373  $path = $this->getModulePath();
2374 
2375  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2376  $msgs = [];
2377  foreach ( $params as $param => $settings ) {
2378  if ( !is_array( $settings ) ) {
2379  $settings = [];
2380  }
2381 
2382  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2383  $msg = $settings[self::PARAM_HELP_MSG];
2384  } else {
2385  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2386  }
2387  $msg = self::makeMessage( $msg, $this->getContext(),
2388  [ $prefix, $param, $name, $path ] );
2389  if ( !$msg ) {
2390  self::dieDebug( __METHOD__,
2391  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2392  }
2393  $msgs[$param] = [ $msg ];
2394 
2395  if ( isset( $settings[self::PARAM_TYPE] ) &&
2396  $settings[self::PARAM_TYPE] === 'submodule'
2397  ) {
2398  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2399  $map = $settings[self::PARAM_SUBMODULE_MAP];
2400  } else {
2401  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2402  $map = [];
2403  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2404  $map[$submoduleName] = $prefix . $submoduleName;
2405  }
2406  }
2407  ksort( $map );
2408  $submodules = [];
2409  $deprecatedSubmodules = [];
2410  foreach ( $map as $v => $m ) {
2411  $arr = &$submodules;
2412  $isDeprecated = false;
2413  $summary = null;
2414  try {
2415  $submod = $this->getModuleFromPath( $m );
2416  if ( $submod ) {
2417  $summary = $submod->getFinalSummary();
2418  $isDeprecated = $submod->isDeprecated();
2419  if ( $isDeprecated ) {
2420  $arr = &$deprecatedSubmodules;
2421  }
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( "[[Special:ApiHelp/$m|$v]]", $key, $params, $isDeprecated );
2434  $arr[] = $m->setContext( $this->getContext() );
2435  }
2436  $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
2437  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2438  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2439  self::dieDebug( __METHOD__,
2440  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2441  }
2442  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2443  self::dieDebug( __METHOD__,
2444  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2445  'ApiBase::PARAM_TYPE is an array' );
2446  }
2447 
2448  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2449  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2450 
2451  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2452  if ( isset( $valueMsgs[$value] ) ) {
2453  $msg = $valueMsgs[$value];
2454  } else {
2455  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2456  }
2457  $m = self::makeMessage( $msg, $this->getContext(),
2458  [ $prefix, $param, $name, $path, $value ] );
2459  if ( $m ) {
2460  $m = new ApiHelpParamValueMessage(
2461  $value,
2462  [ $m->getKey(), 'api-help-param-no-description' ],
2463  $m->getParams(),
2464  isset( $deprecatedValues[$value] )
2465  );
2466  $msgs[$param][] = $m->setContext( $this->getContext() );
2467  } else {
2468  self::dieDebug( __METHOD__,
2469  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2470  }
2471  }
2472  }
2473 
2474  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2475  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2476  self::dieDebug( __METHOD__,
2477  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2478  }
2479  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2480  $m = self::makeMessage( $m, $this->getContext(),
2481  [ $prefix, $param, $name, $path ] );
2482  if ( $m ) {
2483  $msgs[$param][] = $m;
2484  } else {
2485  self::dieDebug( __METHOD__,
2486  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2487  }
2488  }
2489  }
2490  }
2491 
2492  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2493 
2494  return $msgs;
2495  }
2496 
2506  protected function getHelpFlags() {
2507  $flags = [];
2508 
2509  if ( $this->isDeprecated() ) {
2510  $flags[] = 'deprecated';
2511  }
2512  if ( $this->isInternal() ) {
2513  $flags[] = 'internal';
2514  }
2515  if ( $this->isReadMode() ) {
2516  $flags[] = 'readrights';
2517  }
2518  if ( $this->isWriteMode() ) {
2519  $flags[] = 'writerights';
2520  }
2521  if ( $this->mustBePosted() ) {
2522  $flags[] = 'mustbeposted';
2523  }
2524 
2525  return $flags;
2526  }
2527 
2539  protected function getModuleSourceInfo() {
2540  global $IP;
2541 
2542  if ( $this->mModuleSource !== false ) {
2543  return $this->mModuleSource;
2544  }
2545 
2546  // First, try to find where the module comes from...
2547  $rClass = new ReflectionClass( $this );
2548  $path = $rClass->getFileName();
2549  if ( !$path ) {
2550  // No path known?
2551  $this->mModuleSource = null;
2552  return null;
2553  }
2554  $path = realpath( $path ) ?: $path;
2555 
2556  // Build map of extension directories to extension info
2557  if ( self::$extensionInfo === null ) {
2558  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2559  self::$extensionInfo = [
2560  realpath( __DIR__ ) ?: __DIR__ => [
2561  'path' => $IP,
2562  'name' => 'MediaWiki',
2563  'license-name' => 'GPL-2.0-or-later',
2564  ],
2565  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2566  realpath( $extDir ) ?: $extDir => null,
2567  ];
2568  $keep = [
2569  'path' => null,
2570  'name' => null,
2571  'namemsg' => null,
2572  'license-name' => null,
2573  ];
2574  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2575  foreach ( $group as $ext ) {
2576  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2577  // This shouldn't happen, but does anyway.
2578  continue;
2579  }
2580 
2581  $extpath = $ext['path'];
2582  if ( !is_dir( $extpath ) ) {
2583  $extpath = dirname( $extpath );
2584  }
2585  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2586  array_intersect_key( $ext, $keep );
2587  }
2588  }
2589  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2590  $extpath = $ext['path'];
2591  if ( !is_dir( $extpath ) ) {
2592  $extpath = dirname( $extpath );
2593  }
2594  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2595  array_intersect_key( $ext, $keep );
2596  }
2597  }
2598 
2599  // Now traverse parent directories until we find a match or run out of
2600  // parents.
2601  do {
2602  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2603  // Found it!
2604  $this->mModuleSource = self::$extensionInfo[$path];
2605  return $this->mModuleSource;
2606  }
2607 
2608  $oldpath = $path;
2609  $path = dirname( $path );
2610  } while ( $path !== $oldpath );
2611 
2612  // No idea what extension this might be.
2613  $this->mModuleSource = null;
2614  return null;
2615  }
2616 
2628  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2629  }
2630 
2633  /************************************************************************/
2647  protected function getDescription() {
2648  wfDeprecated( __METHOD__, '1.25' );
2649  return false;
2650  }
2651 
2664  protected function getParamDescription() {
2665  wfDeprecated( __METHOD__, '1.25' );
2666  return [];
2667  }
2668 
2685  protected function getExamples() {
2686  wfDeprecated( __METHOD__, '1.25' );
2687  return false;
2688  }
2689 
2698  protected function getDescriptionMessage() {
2699  wfDeprecated( __METHOD__, '1.30' );
2700  return [ [
2701  "apihelp-{$this->getModulePath()}-description",
2702  "apihelp-{$this->getModulePath()}-summary",
2703  ] ];
2704  }
2705 
2707 }
2708 
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
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:1018
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1454
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1070
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:2370
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:892
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:653
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:411
getErrorsByType( $type)
Returns a list of status messages of the given type.
getResult()
Get the result object.
Definition: ApiBase.php:639
Message subclass that prepends wikitext for API help.
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
static $blockMsgMap
$var array Map of web UI block messages to corresponding API messages and codes
Definition: ApiBase.php:281
getDescriptionMessage()
Return the description message.
Definition: ApiBase.php:2698
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2539
$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:353
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:1982
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:2079
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1941
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1865
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1575
getMain()
Get the main module.
Definition: ApiBase.php:535
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:2048
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:667
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:285
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:567
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:553
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:2008
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:760
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:2020
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:292
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1628
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:1700
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:443
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:377
string $mModuleName
Definition: ApiBase.php:291
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:475
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:875
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:1794
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:1233
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:1263
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:1494
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:1098
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:2506
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:958
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:583
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:679
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:693
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:1466
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:302
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:1982
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:453
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1139
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:780
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition: Message.php:724
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:727
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:519
$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:601
string $mModulePrefix
Definition: ApiBase.php:291
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:2628
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:780
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:930
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:1819
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:340
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1759
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:1033
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:738
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:2032
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:434
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:504
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:1974
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:1879
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:527
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2107
const RE_IP_BYTE
Definition: IP.php:29
getDescription()
Returns the description string for this module.
Definition: ApiBase.php:2647
$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:2309
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1927
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:709
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
getExamples()
Returns usage examples for this module.
Definition: ApiBase.php:2685
const ALL_DEFAULT_STRING
Definition: ApiBase.php:256
$mParamCache
Definition: ApiBase.php:293
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:488
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:1665
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1744
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:112
array null bool $mModuleSource
Definition: ApiBase.php:295
const DB_REPLICA
Definition: defines.php:25
getParamDescription()
Returns an array of parameter descriptions.
Definition: ApiBase.php:2664
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:481
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:368
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:988
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:594
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:403
getExtendedDescription()
Return the extended help text message.
Definition: ApiBase.php:2280
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:2633
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:2123
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1987
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:1473
static listParam(array $list, $type='text')
Definition: Message.php:1126
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:1847
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:544
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:426
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:393