MediaWiki  master
ApiBase.php
Go to the documentation of this file.
1 <?php
25 
38 abstract class ApiBase extends ContextSource {
39 
49  const PARAM_DFLT = 0;
50 
52  const PARAM_ISMULTI = 1;
53 
88  const PARAM_TYPE = 2;
89 
91  const PARAM_MAX = 3;
92 
97  const PARAM_MAX2 = 4;
98 
100  const PARAM_MIN = 5;
101 
104 
106  const PARAM_DEPRECATED = 7;
107 
112  const PARAM_REQUIRED = 8;
113 
119 
125  const PARAM_HELP_MSG = 10;
126 
133 
143 
149  const PARAM_VALUE_LINKS = 13;
150 
159 
167 
174 
181  const PARAM_ALL = 17;
182 
188 
194  const PARAM_SENSITIVE = 19;
195 
204 
210 
217 
222  const PARAM_MAX_BYTES = 23;
223 
228  const PARAM_MAX_CHARS = 24;
229 
247 
250  const ALL_DEFAULT_STRING = '*';
251 
253  const LIMIT_BIG1 = 500;
255  const LIMIT_BIG2 = 5000;
257  const LIMIT_SML1 = 50;
259  const LIMIT_SML2 = 500;
260 
267 
269  private static $extensionInfo = null;
270 
272  private static $filterIDsCache = [];
273 
275  private $mMainModule;
278  private $mReplicaDB = null;
279  private $mParamCache = [];
281  private $mModuleSource = false;
282 
288  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
289  $this->mMainModule = $mainModule;
290  $this->mModuleName = $moduleName;
291  $this->mModulePrefix = $modulePrefix;
292 
293  if ( !$this->isMain() ) {
294  $this->setContext( $mainModule->getContext() );
295  }
296  }
297 
298  /************************************************************************/
319  abstract public function execute();
320 
326  public function getModuleManager() {
327  return null;
328  }
329 
339  public function getCustomPrinter() {
340  return null;
341  }
342 
354  protected function getExamplesMessages() {
355  return [];
356  }
357 
363  public function getHelpUrls() {
364  return [];
365  }
366 
379  protected function getAllowedParams( /* $flags = 0 */ ) {
380  // int $flags is not declared because it causes "Strict standards"
381  // warning. Most derived classes do not implement it.
382  return [];
383  }
384 
389  public function shouldCheckMaxlag() {
390  return true;
391  }
392 
397  public function isReadMode() {
398  return true;
399  }
400 
412  public function isWriteMode() {
413  return false;
414  }
415 
420  public function mustBePosted() {
421  return $this->needsToken() !== false;
422  }
423 
429  public function isDeprecated() {
430  return false;
431  }
432 
439  public function isInternal() {
440  return false;
441  }
442 
461  public function needsToken() {
462  return false;
463  }
464 
474  protected function getWebUITokenSalt( array $params ) {
475  return null;
476  }
477 
490  public function getConditionalRequestData( $condition ) {
491  return null;
492  }
493 
496  /************************************************************************/
505  public function getModuleName() {
506  return $this->mModuleName;
507  }
508 
513  public function getModulePrefix() {
514  return $this->mModulePrefix;
515  }
516 
521  public function getMain() {
522  return $this->mMainModule;
523  }
524 
530  public function isMain() {
531  return $this === $this->mMainModule;
532  }
533 
539  public function getParent() {
540  return $this->isMain() ? null : $this->getMain();
541  }
542 
553  public function lacksSameOriginSecurity() {
554  // Main module has this method overridden
555  // Safety - avoid infinite loop:
556  if ( $this->isMain() ) {
557  self::dieDebug( __METHOD__, 'base method was called on main module.' );
558  }
559 
560  return $this->getMain()->lacksSameOriginSecurity();
561  }
562 
569  public function getModulePath() {
570  if ( $this->isMain() ) {
571  return 'main';
572  } elseif ( $this->getParent()->isMain() ) {
573  return $this->getModuleName();
574  } else {
575  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
576  }
577  }
578 
587  public function getModuleFromPath( $path ) {
588  $module = $this->getMain();
589  if ( $path === 'main' ) {
590  return $module;
591  }
592 
593  $parts = explode( '+', $path );
594  if ( count( $parts ) === 1 ) {
595  // In case the '+' was typed into URL, it resolves as a space
596  $parts = explode( ' ', $path );
597  }
598 
599  $count = count( $parts );
600  for ( $i = 0; $i < $count; $i++ ) {
601  $parent = $module;
602  $manager = $parent->getModuleManager();
603  if ( $manager === null ) {
604  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
605  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
606  }
607  $module = $manager->getModule( $parts[$i] );
608 
609  if ( $module === null ) {
610  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
611  $this->dieWithError(
612  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
613  'badmodule'
614  );
615  }
616  }
617 
618  return $module;
619  }
620 
625  public function getResult() {
626  // Main module has getResult() method overridden
627  // Safety - avoid infinite loop:
628  if ( $this->isMain() ) {
629  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
630  }
631 
632  return $this->getMain()->getResult();
633  }
634 
639  public function getErrorFormatter() {
640  // Main module has getErrorFormatter() 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()->getErrorFormatter();
647  }
648 
653  protected function getDB() {
654  if ( !isset( $this->mReplicaDB ) ) {
655  $this->mReplicaDB = wfGetDB( DB_REPLICA, 'api' );
656  }
657 
658  return $this->mReplicaDB;
659  }
660 
665  public function getContinuationManager() {
666  // Main module has getContinuationManager() method overridden
667  // Safety - avoid infinite loop:
668  if ( $this->isMain() ) {
669  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
670  }
671 
672  return $this->getMain()->getContinuationManager();
673  }
674 
679  public function setContinuationManager( ApiContinuationManager $manager = null ) {
680  // Main module has setContinuationManager() 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  $this->getMain()->setContinuationManager( $manager );
687  }
688 
691  /************************************************************************/
703  public function dynamicParameterDocumentation() {
704  return null;
705  }
706 
714  public function encodeParamName( $paramName ) {
715  if ( is_array( $paramName ) ) {
716  return array_map( function ( $name ) {
717  return $this->mModulePrefix . $name;
718  }, $paramName );
719  } else {
720  return $this->mModulePrefix . $paramName;
721  }
722  }
723 
736  public function extractRequestParams( $options = [] ) {
737  if ( is_bool( $options ) ) {
738  $options = [ 'parseLimit' => $options ];
739  }
740  $options += [
741  'parseLimit' => true,
742  'safeMode' => false,
743  ];
744 
745  $parseLimit = (bool)$options['parseLimit'];
746 
747  // Cache parameters, for performance and to avoid T26564.
748  if ( !isset( $this->mParamCache[$parseLimit] ) ) {
749  $params = $this->getFinalParams() ?: [];
750  $results = [];
751  $warned = [];
752 
753  // Process all non-templates and save templates for secondary
754  // processing.
755  $toProcess = [];
756  foreach ( $params as $paramName => $paramSettings ) {
757  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
758  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
759  } else {
760  try {
761  $results[$paramName] = $this->getParameterFromSettings(
762  $paramName, $paramSettings, $parseLimit
763  );
764  } catch ( ApiUsageException $ex ) {
765  $results[$paramName] = $ex;
766  }
767  }
768  }
769 
770  // Now process all the templates by successively replacing the
771  // placeholders with all client-supplied values.
772  // This bit duplicates JavaScript logic in
773  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
774  // If you update this, see if that needs updating too.
775  while ( $toProcess ) {
776  list( $name, $targets, $settings ) = array_shift( $toProcess );
777 
778  foreach ( $targets as $placeholder => $target ) {
779  if ( !array_key_exists( $target, $results ) ) {
780  // The target wasn't processed yet, try the next one.
781  // If all hit this case, the parameter has no expansions.
782  continue;
783  }
784  if ( !is_array( $results[$target] ) || !$results[$target] ) {
785  // The target was processed but has no (valid) values.
786  // That means it has no expansions.
787  break;
788  }
789 
790  // Expand this target in the name and all other targets,
791  // then requeue if there are more targets left or put in
792  // $results if all are done.
793  unset( $targets[$placeholder] );
794  $placeholder = '{' . $placeholder . '}';
795  foreach ( $results[$target] as $value ) {
796  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
797  // Skip values that make invalid parameter names.
798  $encTargetName = $this->encodeParamName( $target );
799  if ( !isset( $warned[$encTargetName][$value] ) ) {
800  $warned[$encTargetName][$value] = true;
801  $this->addWarning( [
802  'apiwarn-ignoring-invalid-templated-value',
803  wfEscapeWikiText( $encTargetName ),
804  wfEscapeWikiText( $value ),
805  ] );
806  }
807  continue;
808  }
809 
810  $newName = str_replace( $placeholder, $value, $name );
811  if ( !$targets ) {
812  try {
813  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
814  } catch ( ApiUsageException $ex ) {
815  $results[$newName] = $ex;
816  }
817  } else {
818  $newTargets = [];
819  foreach ( $targets as $k => $v ) {
820  $newTargets[$k] = str_replace( $placeholder, $value, $v );
821  }
822  $toProcess[] = [ $newName, $newTargets, $settings ];
823  }
824  }
825  break;
826  }
827  }
828 
829  $this->mParamCache[$parseLimit] = $results;
830  }
831 
832  $ret = $this->mParamCache[$parseLimit];
833  if ( !$options['safeMode'] ) {
834  foreach ( $ret as $v ) {
835  if ( $v instanceof ApiUsageException ) {
836  throw $v;
837  }
838  }
839  }
840 
841  return $this->mParamCache[$parseLimit];
842  }
843 
850  protected function getParameter( $paramName, $parseLimit = true ) {
851  $ret = $this->extractRequestParams( [
852  'parseLimit' => $parseLimit,
853  'safeMode' => true,
854  ] )[$paramName];
855  if ( $ret instanceof ApiUsageException ) {
856  throw $ret;
857  }
858  return $ret;
859  }
860 
867  public function requireOnlyOneParameter( $params, $required /*...*/ ) {
868  $required = func_get_args();
869  array_shift( $required );
870 
871  $intersection = array_intersect( array_keys( array_filter( $params,
872  [ $this, 'parameterNotEmpty' ] ) ), $required );
873 
874  if ( count( $intersection ) > 1 ) {
875  $this->dieWithError( [
876  'apierror-invalidparammix',
877  Message::listParam( array_map(
878  function ( $p ) {
879  return '<var>' . $this->encodeParamName( $p ) . '</var>';
880  },
881  array_values( $intersection )
882  ) ),
883  count( $intersection ),
884  ] );
885  } elseif ( count( $intersection ) == 0 ) {
886  $this->dieWithError( [
887  'apierror-missingparam-one-of',
888  Message::listParam( array_map(
889  function ( $p ) {
890  return '<var>' . $this->encodeParamName( $p ) . '</var>';
891  },
892  array_values( $required )
893  ) ),
894  count( $required ),
895  ], 'missingparam' );
896  }
897  }
898 
905  public function requireMaxOneParameter( $params, $required /*...*/ ) {
906  $required = func_get_args();
907  array_shift( $required );
908 
909  $intersection = array_intersect( array_keys( array_filter( $params,
910  [ $this, 'parameterNotEmpty' ] ) ), $required );
911 
912  if ( count( $intersection ) > 1 ) {
913  $this->dieWithError( [
914  'apierror-invalidparammix',
915  Message::listParam( array_map(
916  function ( $p ) {
917  return '<var>' . $this->encodeParamName( $p ) . '</var>';
918  },
919  array_values( $intersection )
920  ) ),
921  count( $intersection ),
922  ] );
923  }
924  }
925 
933  public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
934  $required = func_get_args();
935  array_shift( $required );
936 
937  $intersection = array_intersect(
938  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
939  $required
940  );
941 
942  if ( count( $intersection ) == 0 ) {
943  $this->dieWithError( [
944  'apierror-missingparam-at-least-one-of',
945  Message::listParam( array_map(
946  function ( $p ) {
947  return '<var>' . $this->encodeParamName( $p ) . '</var>';
948  },
949  array_values( $required )
950  ) ),
951  count( $required ),
952  ], 'missingparam' );
953  }
954  }
955 
963  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
964  // Skip if $wgDebugAPI is set or we're in internal mode
965  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
966  return;
967  }
968 
969  $queryValues = $this->getRequest()->getQueryValues();
970  $badParams = [];
971  foreach ( $params as $param ) {
972  if ( $prefix !== 'noprefix' ) {
973  $param = $this->encodeParamName( $param );
974  }
975  if ( array_key_exists( $param, $queryValues ) ) {
976  $badParams[] = $param;
977  }
978  }
979 
980  if ( $badParams ) {
981  $this->dieWithError(
982  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
983  );
984  }
985  }
986 
993  private function parameterNotEmpty( $x ) {
994  return !is_null( $x ) && $x !== false;
995  }
996 
1008  public function getTitleOrPageId( $params, $load = false ) {
1009  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1010 
1011  $pageObj = null;
1012  if ( isset( $params['title'] ) ) {
1013  $titleObj = Title::newFromText( $params['title'] );
1014  if ( !$titleObj || $titleObj->isExternal() ) {
1015  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1016  }
1017  if ( !$titleObj->canExist() ) {
1018  $this->dieWithError( 'apierror-pagecannotexist' );
1019  }
1020  $pageObj = WikiPage::factory( $titleObj );
1021  if ( $load !== false ) {
1022  $pageObj->loadPageData( $load );
1023  }
1024  } elseif ( isset( $params['pageid'] ) ) {
1025  if ( $load === false ) {
1026  $load = 'fromdb';
1027  }
1028  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1029  if ( !$pageObj ) {
1030  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1031  }
1032  }
1033 
1034  return $pageObj;
1035  }
1036 
1045  public function getTitleFromTitleOrPageId( $params ) {
1046  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1047 
1048  $titleObj = null;
1049  if ( isset( $params['title'] ) ) {
1050  $titleObj = Title::newFromText( $params['title'] );
1051  if ( !$titleObj || $titleObj->isExternal() ) {
1052  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1053  }
1054  return $titleObj;
1055  } elseif ( isset( $params['pageid'] ) ) {
1056  $titleObj = Title::newFromID( $params['pageid'] );
1057  if ( !$titleObj ) {
1058  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1059  }
1060  }
1061 
1062  return $titleObj;
1063  }
1064 
1073  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1074  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1075 
1076  switch ( $watchlist ) {
1077  case 'watch':
1078  return true;
1079 
1080  case 'unwatch':
1081  return false;
1082 
1083  case 'preferences':
1084  # If the user is already watching, don't bother checking
1085  if ( $userWatching ) {
1086  return true;
1087  }
1088  # If no user option was passed, use watchdefault and watchcreations
1089  if ( is_null( $userOption ) ) {
1090  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1091  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1092  }
1093 
1094  # Watch the article based on the user preference
1095  return $this->getUser()->getBoolOption( $userOption );
1096 
1097  case 'nochange':
1098  return $userWatching;
1099 
1100  default:
1101  return $userWatching;
1102  }
1103  }
1104 
1114  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1115  // Some classes may decide to change parameter names
1116  $encParamName = $this->encodeParamName( $paramName );
1117 
1118  // Shorthand
1119  if ( !is_array( $paramSettings ) ) {
1120  $paramSettings = [
1121  self::PARAM_DFLT => $paramSettings,
1122  ];
1123  }
1124 
1125  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1126  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1127  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1128  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1129  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1130  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1131  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1132  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1133  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1134  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1135 
1136  // When type is not given, and no choices, the type is the same as $default
1137  if ( !isset( $type ) ) {
1138  if ( isset( $default ) ) {
1139  $type = gettype( $default );
1140  } else {
1141  $type = 'NULL'; // allow everything
1142  }
1143  }
1144 
1145  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1146  $this->getMain()->markParamsSensitive( $encParamName );
1147  }
1148 
1149  if ( $type == 'boolean' ) {
1150  if ( isset( $default ) && $default !== false ) {
1151  // Having a default value of anything other than 'false' is not allowed
1152  self::dieDebug(
1153  __METHOD__,
1154  "Boolean param $encParamName's default is set to '$default'. " .
1155  'Boolean parameters must default to false.'
1156  );
1157  }
1158 
1159  $value = $this->getMain()->getCheck( $encParamName );
1160  $provided = $value;
1161  } elseif ( $type == 'upload' ) {
1162  if ( isset( $default ) ) {
1163  // Having a default value is not allowed
1164  self::dieDebug(
1165  __METHOD__,
1166  "File upload param $encParamName's default is set to " .
1167  "'$default'. File upload parameters may not have a default." );
1168  }
1169  if ( $multi ) {
1170  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1171  }
1172  $value = $this->getMain()->getUpload( $encParamName );
1173  $provided = $value->exists();
1174  if ( !$value->exists() ) {
1175  // This will get the value without trying to normalize it
1176  // (because trying to normalize a large binary file
1177  // accidentally uploaded as a field fails spectacularly)
1178  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1179  if ( $value !== null ) {
1180  $this->dieWithError(
1181  [ 'apierror-badupload', $encParamName ],
1182  "badupload_{$encParamName}"
1183  );
1184  }
1185  }
1186  } else {
1187  $value = $this->getMain()->getVal( $encParamName, $default );
1188  $provided = $this->getMain()->getCheck( $encParamName );
1189 
1190  if ( isset( $value ) && $type == 'namespace' ) {
1192  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1193  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1194  ) {
1195  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1196  }
1197  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1198  // specify all namespaces irrespective of PARAM_ALL.
1199  $allowAll = true;
1200  }
1201  if ( isset( $value ) && $type == 'submodule' ) {
1202  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1203  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1204  } else {
1205  $type = $this->getModuleManager()->getNames( $paramName );
1206  }
1207  }
1208 
1209  $request = $this->getMain()->getRequest();
1210  $rawValue = $request->getRawVal( $encParamName );
1211  if ( $rawValue === null ) {
1212  $rawValue = $default;
1213  }
1214 
1215  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1216  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1217  if ( $multi ) {
1218  // This loses the potential checkTitleEncoding() transformation done by
1219  // WebRequest for $_GET. Let's call that a feature.
1220  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1221  } else {
1222  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1223  }
1224  }
1225 
1226  // Check for NFC normalization, and warn
1227  if ( $rawValue !== $value ) {
1228  $this->handleParamNormalization( $paramName, $value, $rawValue );
1229  }
1230  }
1231 
1232  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1233  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1234  self::dieDebug(
1235  __METHOD__,
1236  "For param $encParamName, PARAM_ALL collides with a possible value" );
1237  }
1238  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1239  $value = $this->parseMultiValue(
1240  $encParamName,
1241  $value,
1242  $multi,
1243  is_array( $type ) ? $type : null,
1244  $allowAll ? $allSpecifier : null,
1245  $multiLimit1,
1246  $multiLimit2
1247  );
1248  }
1249 
1250  if ( isset( $value ) ) {
1251  // More validation only when choices were not given
1252  // choices were validated in parseMultiValue()
1253  if ( !is_array( $type ) ) {
1254  switch ( $type ) {
1255  case 'NULL': // nothing to do
1256  break;
1257  case 'string':
1258  case 'text':
1259  case 'password':
1260  if ( $required && $value === '' ) {
1261  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1262  }
1263  break;
1264  case 'integer': // Force everything using intval() and optionally validate limits
1265  $min = $paramSettings[self::PARAM_MIN] ?? null;
1266  $max = $paramSettings[self::PARAM_MAX] ?? null;
1267  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1268 
1269  if ( is_array( $value ) ) {
1270  $value = array_map( 'intval', $value );
1271  if ( !is_null( $min ) || !is_null( $max ) ) {
1272  foreach ( $value as &$v ) {
1273  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1274  }
1275  }
1276  } else {
1277  $value = intval( $value );
1278  if ( !is_null( $min ) || !is_null( $max ) ) {
1279  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1280  }
1281  }
1282  break;
1283  case 'limit':
1284  if ( !$parseLimit ) {
1285  // Don't do any validation whatsoever
1286  break;
1287  }
1288  if ( !isset( $paramSettings[self::PARAM_MAX] )
1289  || !isset( $paramSettings[self::PARAM_MAX2] )
1290  ) {
1291  self::dieDebug(
1292  __METHOD__,
1293  "MAX1 or MAX2 are not defined for the limit $encParamName"
1294  );
1295  }
1296  if ( $multi ) {
1297  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1298  }
1299  $min = $paramSettings[self::PARAM_MIN] ?? 0;
1300  if ( $value == 'max' ) {
1301  $value = $this->getMain()->canApiHighLimits()
1302  ? $paramSettings[self::PARAM_MAX2]
1303  : $paramSettings[self::PARAM_MAX];
1304  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1305  } else {
1306  $value = intval( $value );
1307  $this->validateLimit(
1308  $paramName,
1309  $value,
1310  $min,
1311  $paramSettings[self::PARAM_MAX],
1312  $paramSettings[self::PARAM_MAX2]
1313  );
1314  }
1315  break;
1316  case 'boolean':
1317  if ( $multi ) {
1318  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1319  }
1320  break;
1321  case 'timestamp':
1322  if ( is_array( $value ) ) {
1323  foreach ( $value as $key => $val ) {
1324  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1325  }
1326  } else {
1327  $value = $this->validateTimestamp( $value, $encParamName );
1328  }
1329  break;
1330  case 'user':
1331  if ( is_array( $value ) ) {
1332  foreach ( $value as $key => $val ) {
1333  $value[$key] = $this->validateUser( $val, $encParamName );
1334  }
1335  } else {
1336  $value = $this->validateUser( $value, $encParamName );
1337  }
1338  break;
1339  case 'upload': // nothing to do
1340  break;
1341  case 'tags':
1342  // If change tagging was requested, check that the tags are valid.
1343  if ( !is_array( $value ) && !$multi ) {
1344  $value = [ $value ];
1345  }
1347  if ( !$tagsStatus->isGood() ) {
1348  $this->dieStatus( $tagsStatus );
1349  }
1350  break;
1351  default:
1352  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1353  }
1354  }
1355 
1356  // Throw out duplicates if requested
1357  if ( !$dupes && is_array( $value ) ) {
1358  $value = array_unique( $value );
1359  }
1360 
1361  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1362  foreach ( (array)$value as $val ) {
1363  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1364  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1365  ) {
1366  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1367  $paramSettings[self::PARAM_MAX_BYTES] ] );
1368  }
1369  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1370  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1371  ) {
1372  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1373  $paramSettings[self::PARAM_MAX_CHARS] ] );
1374  }
1375  }
1376  }
1377 
1378  // Set a warning if a deprecated parameter has been passed
1379  if ( $deprecated && $provided ) {
1380  $feature = $encParamName;
1381  $m = $this;
1382  while ( !$m->isMain() ) {
1383  $p = $m->getParent();
1384  $name = $m->getModuleName();
1385  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1386  $feature = "{$param}={$name}&{$feature}";
1387  $m = $p;
1388  }
1389  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1390  }
1391 
1392  // Set a warning if a deprecated parameter value has been passed
1393  $usedDeprecatedValues = $deprecatedValues && $provided
1394  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1395  : [];
1396  if ( $usedDeprecatedValues ) {
1397  $feature = "$encParamName=";
1398  $m = $this;
1399  while ( !$m->isMain() ) {
1400  $p = $m->getParent();
1401  $name = $m->getModuleName();
1402  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1403  $feature = "{$param}={$name}&{$feature}";
1404  $m = $p;
1405  }
1406  foreach ( $usedDeprecatedValues as $v ) {
1407  $msg = $deprecatedValues[$v];
1408  if ( $msg === true ) {
1409  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1410  }
1411  $this->addDeprecation( $msg, "$feature$v" );
1412  }
1413  }
1414  } elseif ( $required ) {
1415  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1416  }
1417 
1418  return $value;
1419  }
1420 
1428  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1429  $encParamName = $this->encodeParamName( $paramName );
1430  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1431  }
1432 
1440  protected function explodeMultiValue( $value, $limit ) {
1441  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1442  $sep = "\x1f";
1443  $value = substr( $value, 1 );
1444  } else {
1445  $sep = '|';
1446  }
1447 
1448  return explode( $sep, $value, $limit );
1449  }
1450 
1468  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1469  $allSpecifier = null, $limit1 = null, $limit2 = null
1470  ) {
1471  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1472  return [];
1473  }
1474  $limit1 = $limit1 ?: self::LIMIT_SML1;
1475  $limit2 = $limit2 ?: self::LIMIT_SML2;
1476 
1477  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1478  // because it unstubs $wgUser
1479  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1480  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1481  ? $limit2
1482  : $limit1;
1483 
1484  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1485  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1486  ) {
1487  return $allowedValues;
1488  }
1489 
1490  if ( count( $valuesList ) > $sizeLimit ) {
1491  $this->dieWithError(
1492  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1493  "too-many-$valueName"
1494  );
1495  }
1496 
1497  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1498  // T35482 - Allow entries with | in them for non-multiple values
1499  if ( in_array( $value, $allowedValues, true ) ) {
1500  return $value;
1501  }
1502 
1503  $values = array_map( function ( $v ) {
1504  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1505  }, $allowedValues );
1506  $this->dieWithError( [
1507  'apierror-multival-only-one-of',
1508  $valueName,
1509  Message::listParam( $values ),
1510  count( $values ),
1511  ], "multival_$valueName" );
1512  }
1513 
1514  if ( is_array( $allowedValues ) ) {
1515  // Check for unknown values
1516  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1517  if ( count( $unknown ) ) {
1518  if ( $allowMultiple ) {
1519  $this->addWarning( [
1520  'apiwarn-unrecognizedvalues',
1521  $valueName,
1522  Message::listParam( $unknown, 'comma' ),
1523  count( $unknown ),
1524  ] );
1525  } else {
1526  $this->dieWithError(
1527  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1528  "unknown_$valueName"
1529  );
1530  }
1531  }
1532  // Now throw them out
1533  $valuesList = array_intersect( $valuesList, $allowedValues );
1534  }
1535 
1536  return $allowMultiple ? $valuesList : $valuesList[0];
1537  }
1538 
1549  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1550  $enforceLimits = false
1551  ) {
1552  if ( !is_null( $min ) && $value < $min ) {
1553  $msg = ApiMessage::create(
1554  [ 'apierror-integeroutofrange-belowminimum',
1555  $this->encodeParamName( $paramName ), $min, $value ],
1556  'integeroutofrange',
1557  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1558  );
1559  $this->warnOrDie( $msg, $enforceLimits );
1560  $value = $min;
1561  }
1562 
1563  // Minimum is always validated, whereas maximum is checked only if not
1564  // running in internal call mode
1565  if ( $this->getMain()->isInternalMode() ) {
1566  return;
1567  }
1568 
1569  // Optimization: do not check user's bot status unless really needed -- skips db query
1570  // assumes $botMax >= $max
1571  if ( !is_null( $max ) && $value > $max ) {
1572  if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1573  if ( $value > $botMax ) {
1574  $msg = ApiMessage::create(
1575  [ 'apierror-integeroutofrange-abovebotmax',
1576  $this->encodeParamName( $paramName ), $botMax, $value ],
1577  'integeroutofrange',
1578  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1579  );
1580  $this->warnOrDie( $msg, $enforceLimits );
1581  $value = $botMax;
1582  }
1583  } else {
1584  $msg = ApiMessage::create(
1585  [ 'apierror-integeroutofrange-abovemax',
1586  $this->encodeParamName( $paramName ), $max, $value ],
1587  'integeroutofrange',
1588  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1589  );
1590  $this->warnOrDie( $msg, $enforceLimits );
1591  $value = $max;
1592  }
1593  }
1594  }
1595 
1602  protected function validateTimestamp( $value, $encParamName ) {
1603  // Confusing synonyms for the current time accepted by wfTimestamp()
1604  // (wfTimestamp() also accepts various non-strings and the string of 14
1605  // ASCII NUL bytes, but those can't get here)
1606  if ( !$value ) {
1607  $this->addDeprecation(
1608  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1609  'unclear-"now"-timestamp'
1610  );
1611  return wfTimestamp( TS_MW );
1612  }
1613 
1614  // Explicit synonym for the current time
1615  if ( $value === 'now' ) {
1616  return wfTimestamp( TS_MW );
1617  }
1618 
1619  $timestamp = wfTimestamp( TS_MW, $value );
1620  if ( $timestamp === false ) {
1621  $this->dieWithError(
1622  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1623  "badtimestamp_{$encParamName}"
1624  );
1625  }
1626 
1627  return $timestamp;
1628  }
1629 
1639  final public function validateToken( $token, array $params ) {
1640  $tokenType = $this->needsToken();
1642  if ( !isset( $salts[$tokenType] ) ) {
1643  throw new MWException(
1644  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1645  'without registering it'
1646  );
1647  }
1648 
1649  $tokenObj = ApiQueryTokens::getToken(
1650  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1651  );
1652  if ( $tokenObj->match( $token ) ) {
1653  return true;
1654  }
1655 
1656  $webUiSalt = $this->getWebUITokenSalt( $params );
1657  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1658  $token,
1659  $webUiSalt,
1660  $this->getRequest()
1661  ) ) {
1662  return true;
1663  }
1664 
1665  return false;
1666  }
1667 
1674  private function validateUser( $value, $encParamName ) {
1676  return $value;
1677  }
1678 
1679  $name = User::getCanonicalName( $value, 'valid' );
1680  if ( $name !== false ) {
1681  return $name;
1682  }
1683 
1684  if (
1685  // We allow ranges as well, for blocks.
1686  IP::isIPAddress( $value ) ||
1687  // See comment for User::isIP. We don't just call that function
1688  // here because it also returns true for things like
1689  // 300.300.300.300 that are neither valid usernames nor valid IP
1690  // addresses.
1691  preg_match(
1692  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1693  $value
1694  )
1695  ) {
1696  return IP::sanitizeIP( $value );
1697  }
1698 
1699  $this->dieWithError(
1700  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1701  "baduser_{$encParamName}"
1702  );
1703  }
1704 
1707  /************************************************************************/
1718  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1719  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1720  if ( $value === null ) {
1721  return;
1722  }
1723 
1724  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1725  }
1726 
1733  public function getWatchlistUser( $params ) {
1734  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1735  $user = User::newFromName( $params['owner'], false );
1736  if ( !( $user && $user->getId() ) ) {
1737  $this->dieWithError(
1738  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1739  );
1740  }
1741  $token = $user->getOption( 'watchlisttoken' );
1742  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1743  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1744  }
1745  } else {
1746  if ( !$this->getUser()->isLoggedIn() ) {
1747  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1748  }
1749  $this->checkUserRightsAny( 'viewmywatchlist' );
1750  $user = $this->getUser();
1751  }
1752 
1753  return $user;
1754  }
1755 
1768  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1769  if ( is_string( $msg ) ) {
1770  $msg = wfMessage( $msg );
1771  } elseif ( is_array( $msg ) ) {
1772  $msg = wfMessage( ...$msg );
1773  }
1774  if ( !$msg instanceof Message ) {
1775  return null;
1776  }
1777 
1778  $msg->setContext( $context );
1779  if ( $params ) {
1780  $msg->params( $params );
1781  }
1782 
1783  return $msg;
1784  }
1785 
1793  public function errorArrayToStatus( array $errors, User $user = null ) {
1794  if ( $user === null ) {
1795  $user = $this->getUser();
1796  }
1797 
1799  foreach ( $errors as $error ) {
1800  if ( is_array( $error ) && $error[0] === 'blockedtext' && $user->getBlock() ) {
1801  $status->fatal( ApiMessage::create(
1802  'apierror-blocked',
1803  'blocked',
1804  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1805  ) );
1806  } elseif ( is_array( $error ) && $error[0] === 'blockedtext-partial' && $user->getBlock() ) {
1807  $status->fatal( ApiMessage::create(
1808  'apierror-blocked-partial',
1809  'blocked',
1810  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1811  ) );
1812  } elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
1813  $status->fatal( ApiMessage::create(
1814  'apierror-autoblocked',
1815  'autoblocked',
1816  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1817  ) );
1818  } elseif ( is_array( $error ) && $error[0] === 'systemblockedtext' && $user->getBlock() ) {
1819  $status->fatal( ApiMessage::create(
1820  'apierror-systemblocked',
1821  'blocked',
1822  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1823  ) );
1824  } else {
1825  $status->fatal( ...(array)$error );
1826  }
1827  }
1828  return $status;
1829  }
1830 
1835  protected function useTransactionalTimeLimit() {
1836  if ( $this->getRequest()->wasPosted() ) {
1838  }
1839  }
1840 
1849  protected function filterIDs( $fields, array $ids ) {
1850  $min = INF;
1851  $max = 0;
1852  foreach ( $fields as list( $table, $field ) ) {
1853  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1854  $row = self::$filterIDsCache[$table][$field];
1855  } else {
1856  $row = $this->getDB()->selectRow(
1857  $table,
1858  [
1859  'min_id' => "MIN($field)",
1860  'max_id' => "MAX($field)",
1861  ],
1862  '',
1863  __METHOD__
1864  );
1865  self::$filterIDsCache[$table][$field] = $row;
1866  }
1867  $min = min( $min, $row->min_id );
1868  $max = max( $max, $row->max_id );
1869  }
1870  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1871  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1872  && $id >= $min && $id <= $max;
1873  } );
1874  }
1875 
1878  /************************************************************************/
1897  public function addWarning( $msg, $code = null, $data = null ) {
1898  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1899  }
1900 
1911  public function addDeprecation( $msg, $feature, $data = [] ) {
1912  $data = (array)$data;
1913  if ( $feature !== null ) {
1914  $data['feature'] = $feature;
1915  $this->logFeatureUsage( $feature );
1916  }
1917  $this->addWarning( $msg, 'deprecation', $data );
1918 
1919  // No real need to deduplicate here, ApiErrorFormatter does that for
1920  // us (assuming the hook is deterministic).
1921  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1922  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1923  if ( count( $msgs ) > 1 ) {
1924  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1925  $msg = ( new RawMessage( $key ) )->params( $msgs );
1926  } else {
1927  $msg = reset( $msgs );
1928  }
1929  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1930  }
1931 
1944  public function addError( $msg, $code = null, $data = null ) {
1945  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1946  }
1947 
1957  public function addMessagesFromStatus(
1958  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1959  ) {
1960  $this->getErrorFormatter()->addMessagesFromStatus(
1961  $this->getModulePath(), $status, $types, $filter
1962  );
1963  }
1964 
1978  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
1979  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
1980  }
1981 
1990  public function dieWithException( $exception, array $options = [] ) {
1991  $this->dieWithError(
1992  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
1993  );
1994  }
1995 
2002  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2003  if ( $enforceLimits ) {
2004  $this->dieWithError( $msg );
2005  } else {
2006  $this->addWarning( $msg );
2007  }
2008  }
2009 
2018  public function dieBlocked( Block $block ) {
2019  // Die using the appropriate message depending on block type
2020  if ( $block->getType() == Block::TYPE_AUTO ) {
2021  $this->dieWithError(
2022  'apierror-autoblocked',
2023  'autoblocked',
2024  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2025  );
2026  } elseif ( !$block->isSitewide() ) {
2027  $this->dieWithError(
2028  'apierror-blocked-partial',
2029  'blocked',
2030  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2031  );
2032  } else {
2033  $this->dieWithError(
2034  'apierror-blocked',
2035  'blocked',
2036  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2037  );
2038  }
2039  }
2040 
2049  public function dieStatus( StatusValue $status ) {
2050  if ( $status->isGood() ) {
2051  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2052  }
2053 
2054  // ApiUsageException needs a fatal status, but this method has
2055  // historically accepted any non-good status. Convert it if necessary.
2056  $status->setOK( false );
2057  if ( !$status->getErrorsByType( 'error' ) ) {
2058  $newStatus = Status::newGood();
2059  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2060  $newStatus->fatal( $err['message'], ...$err['params'] );
2061  }
2062  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2063  $newStatus->fatal( 'unknownerror-nocode' );
2064  }
2065  $status = $newStatus;
2066  }
2067 
2068  throw new ApiUsageException( $this, $status );
2069  }
2070 
2076  public function dieReadOnly() {
2077  $this->dieWithError(
2078  'apierror-readonly',
2079  'readonly',
2080  [ 'readonlyreason' => wfReadOnlyReason() ]
2081  );
2082  }
2083 
2092  public function checkUserRightsAny( $rights, $user = null ) {
2093  if ( !$user ) {
2094  $user = $this->getUser();
2095  }
2096  $rights = (array)$rights;
2097  if ( !$user->isAllowedAny( ...$rights ) ) {
2098  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2099  }
2100  }
2101 
2110  public function checkTitleUserPermissions( Title $title, $actions, $user = null ) {
2111  if ( !$user ) {
2112  $user = $this->getUser();
2113  }
2114 
2115  $errors = [];
2116  foreach ( (array)$actions as $action ) {
2117  $errors = array_merge( $errors, $title->getUserPermissionsErrors( $action, $user ) );
2118  }
2119 
2120  if ( $errors ) {
2121  // track block notices
2122  if ( $this->getConfig()->get( 'EnableBlockNoticeStats' ) ) {
2123  $this->trackBlockNotices( $errors );
2124  }
2125 
2126  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2127  }
2128  }
2129 
2135  private function trackBlockNotices( array $errors ) {
2136  $errorMessageKeys = [
2137  'blockedtext',
2138  'blockedtext-partial',
2139  'autoblockedtext',
2140  'systemblockedtext',
2141  ];
2142 
2143  $statsd = MediaWikiServices::getInstance()->getStatsdDataFactory();
2144 
2145  foreach ( $errors as $error ) {
2146  if ( in_array( $error[0], $errorMessageKeys ) ) {
2147  $wiki = $this->getConfig()->get( 'DBname' );
2148  $statsd->increment( 'BlockNotices.' . $wiki . '.MediaWikiApi.returned' );
2149  break;
2150  }
2151  }
2152  }
2153 
2165  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2166  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2167  $this->dieWithError( $msg, $code, $data, $httpCode );
2168  } else {
2169  $this->addWarning( $msg, $code, $data );
2170  }
2171  }
2172 
2182  protected function dieContinueUsageIf( $condition ) {
2183  if ( $condition ) {
2184  $this->dieWithError( 'apierror-badcontinue' );
2185  }
2186  }
2187 
2194  protected static function dieDebug( $method, $message ) {
2195  throw new MWException( "Internal error in $method: $message" );
2196  }
2197 
2204  public function logFeatureUsage( $feature ) {
2205  $request = $this->getRequest();
2206  $s = '"' . addslashes( $feature ) . '"' .
2207  ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
2208  ' "' . $request->getIP() . '"' .
2209  ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
2210  ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
2211  wfDebugLog( 'api-feature-usage', $s, 'private' );
2212  }
2213 
2216  /************************************************************************/
2230  protected function getSummaryMessage() {
2231  return "apihelp-{$this->getModulePath()}-summary";
2232  }
2233 
2243  protected function getExtendedDescription() {
2244  return [ [
2245  "apihelp-{$this->getModulePath()}-extended-description",
2246  'api-help-no-extended-description',
2247  ] ];
2248  }
2249 
2256  public function getFinalSummary() {
2257  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2258  $this->getModulePrefix(),
2259  $this->getModuleName(),
2260  $this->getModulePath(),
2261  ] );
2262  return $msg;
2263  }
2264 
2272  public function getFinalDescription() {
2273  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2274  $this->getModulePrefix(),
2275  $this->getModuleName(),
2276  $this->getModulePath(),
2277  ] );
2278  $extendedDescription = self::makeMessage(
2279  $this->getExtendedDescription(), $this->getContext(), [
2280  $this->getModulePrefix(),
2281  $this->getModuleName(),
2282  $this->getModulePath(),
2283  ]
2284  );
2285 
2286  $msgs = [ $summary, $extendedDescription ];
2287 
2288  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2289 
2290  return $msgs;
2291  }
2292 
2301  public function getFinalParams( $flags = 0 ) {
2302  $params = $this->getAllowedParams( $flags );
2303  if ( !$params ) {
2304  $params = [];
2305  }
2306 
2307  if ( $this->needsToken() ) {
2308  $params['token'] = [
2309  self::PARAM_TYPE => 'string',
2310  self::PARAM_REQUIRED => true,
2311  self::PARAM_SENSITIVE => true,
2312  self::PARAM_HELP_MSG => [
2313  'api-help-param-token',
2314  $this->needsToken(),
2315  ],
2316  ] + ( $params['token'] ?? [] );
2317  }
2318 
2319  // Avoid PHP 7.1 warning of passing $this by reference
2320  $apiModule = $this;
2321  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2322 
2323  return $params;
2324  }
2325 
2333  public function getFinalParamDescription() {
2334  $prefix = $this->getModulePrefix();
2335  $name = $this->getModuleName();
2336  $path = $this->getModulePath();
2337 
2338  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2339  $msgs = [];
2340  foreach ( $params as $param => $settings ) {
2341  if ( !is_array( $settings ) ) {
2342  $settings = [];
2343  }
2344 
2345  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2346  $msg = $settings[self::PARAM_HELP_MSG];
2347  } else {
2348  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2349  }
2350  $msg = self::makeMessage( $msg, $this->getContext(),
2351  [ $prefix, $param, $name, $path ] );
2352  if ( !$msg ) {
2353  self::dieDebug( __METHOD__,
2354  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2355  }
2356  $msgs[$param] = [ $msg ];
2357 
2358  if ( isset( $settings[self::PARAM_TYPE] ) &&
2359  $settings[self::PARAM_TYPE] === 'submodule'
2360  ) {
2361  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2362  $map = $settings[self::PARAM_SUBMODULE_MAP];
2363  } else {
2364  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2365  $map = [];
2366  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2367  $map[$submoduleName] = $prefix . $submoduleName;
2368  }
2369  }
2370  ksort( $map );
2371  $submodules = [];
2372  $deprecatedSubmodules = [];
2373  foreach ( $map as $v => $m ) {
2374  $arr = &$submodules;
2375  $isDeprecated = false;
2376  $summary = null;
2377  try {
2378  $submod = $this->getModuleFromPath( $m );
2379  if ( $submod ) {
2380  $summary = $submod->getFinalSummary();
2381  $isDeprecated = $submod->isDeprecated();
2382  if ( $isDeprecated ) {
2383  $arr = &$deprecatedSubmodules;
2384  }
2385  }
2386  } catch ( ApiUsageException $ex ) {
2387  // Ignore
2388  }
2389  if ( $summary ) {
2390  $key = $summary->getKey();
2391  $params = $summary->getParams();
2392  } else {
2393  $key = 'api-help-undocumented-module';
2394  $params = [ $m ];
2395  }
2396  $m = new ApiHelpParamValueMessage( "[[Special:ApiHelp/$m|$v]]", $key, $params, $isDeprecated );
2397  $arr[] = $m->setContext( $this->getContext() );
2398  }
2399  $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
2400  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2401  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2402  self::dieDebug( __METHOD__,
2403  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2404  }
2405  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2406  self::dieDebug( __METHOD__,
2407  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2408  'ApiBase::PARAM_TYPE is an array' );
2409  }
2410 
2411  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2412  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2413 
2414  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2415  if ( isset( $valueMsgs[$value] ) ) {
2416  $msg = $valueMsgs[$value];
2417  } else {
2418  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2419  }
2420  $m = self::makeMessage( $msg, $this->getContext(),
2421  [ $prefix, $param, $name, $path, $value ] );
2422  if ( $m ) {
2423  $m = new ApiHelpParamValueMessage(
2424  $value,
2425  [ $m->getKey(), 'api-help-param-no-description' ],
2426  $m->getParams(),
2427  isset( $deprecatedValues[$value] )
2428  );
2429  $msgs[$param][] = $m->setContext( $this->getContext() );
2430  } else {
2431  self::dieDebug( __METHOD__,
2432  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2433  }
2434  }
2435  }
2436 
2437  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2438  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2439  self::dieDebug( __METHOD__,
2440  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2441  }
2442  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2443  $m = self::makeMessage( $m, $this->getContext(),
2444  [ $prefix, $param, $name, $path ] );
2445  if ( $m ) {
2446  $msgs[$param][] = $m;
2447  } else {
2448  self::dieDebug( __METHOD__,
2449  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2450  }
2451  }
2452  }
2453  }
2454 
2455  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2456 
2457  return $msgs;
2458  }
2459 
2469  protected function getHelpFlags() {
2470  $flags = [];
2471 
2472  if ( $this->isDeprecated() ) {
2473  $flags[] = 'deprecated';
2474  }
2475  if ( $this->isInternal() ) {
2476  $flags[] = 'internal';
2477  }
2478  if ( $this->isReadMode() ) {
2479  $flags[] = 'readrights';
2480  }
2481  if ( $this->isWriteMode() ) {
2482  $flags[] = 'writerights';
2483  }
2484  if ( $this->mustBePosted() ) {
2485  $flags[] = 'mustbeposted';
2486  }
2487 
2488  return $flags;
2489  }
2490 
2502  protected function getModuleSourceInfo() {
2503  global $IP;
2504 
2505  if ( $this->mModuleSource !== false ) {
2506  return $this->mModuleSource;
2507  }
2508 
2509  // First, try to find where the module comes from...
2510  $rClass = new ReflectionClass( $this );
2511  $path = $rClass->getFileName();
2512  if ( !$path ) {
2513  // No path known?
2514  $this->mModuleSource = null;
2515  return null;
2516  }
2517  $path = realpath( $path ) ?: $path;
2518 
2519  // Build map of extension directories to extension info
2520  if ( self::$extensionInfo === null ) {
2521  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2522  self::$extensionInfo = [
2523  realpath( __DIR__ ) ?: __DIR__ => [
2524  'path' => $IP,
2525  'name' => 'MediaWiki',
2526  'license-name' => 'GPL-2.0-or-later',
2527  ],
2528  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2529  realpath( $extDir ) ?: $extDir => null,
2530  ];
2531  $keep = [
2532  'path' => null,
2533  'name' => null,
2534  'namemsg' => null,
2535  'license-name' => null,
2536  ];
2537  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2538  foreach ( $group as $ext ) {
2539  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2540  // This shouldn't happen, but does anyway.
2541  continue;
2542  }
2543 
2544  $extpath = $ext['path'];
2545  if ( !is_dir( $extpath ) ) {
2546  $extpath = dirname( $extpath );
2547  }
2548  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2549  array_intersect_key( $ext, $keep );
2550  }
2551  }
2552  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2553  $extpath = $ext['path'];
2554  if ( !is_dir( $extpath ) ) {
2555  $extpath = dirname( $extpath );
2556  }
2557  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2558  array_intersect_key( $ext, $keep );
2559  }
2560  }
2561 
2562  // Now traverse parent directories until we find a match or run out of
2563  // parents.
2564  do {
2565  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2566  // Found it!
2567  $this->mModuleSource = self::$extensionInfo[$path];
2568  return $this->mModuleSource;
2569  }
2570 
2571  $oldpath = $path;
2572  $path = dirname( $path );
2573  } while ( $path !== $oldpath );
2574 
2575  // No idea what extension this might be.
2576  $this->mModuleSource = null;
2577  return null;
2578  }
2579 
2591  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2592  }
2593 
2596  /************************************************************************/
2610  protected function getDescription() {
2611  wfDeprecated( __METHOD__, '1.25' );
2612  return false;
2613  }
2614 
2627  protected function getParamDescription() {
2628  wfDeprecated( __METHOD__, '1.25' );
2629  return [];
2630  }
2631 
2648  protected function getExamples() {
2649  wfDeprecated( __METHOD__, '1.25' );
2650  return false;
2651  }
2652 
2661  protected function getDescriptionMessage() {
2662  wfDeprecated( __METHOD__, '1.30' );
2663  return [ [
2664  "apihelp-{$this->getModulePath()}-description",
2665  "apihelp-{$this->getModulePath()}-summary",
2666  ] ];
2667  }
2668 
2676  public static function truncateArray( &$arr, $limit ) {
2677  wfDeprecated( __METHOD__, '1.32' );
2678  $modified = false;
2679  while ( count( $arr ) > $limit ) {
2680  array_pop( $arr );
2681  $modified = true;
2682  }
2683 
2684  return $modified;
2685  }
2686 
2688 }
2689 
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:128
setContext(IContextSource $context)
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set...
Definition: ApiBase.php:993
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1428
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1045
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:149
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2333
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:88
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false. ...
Definition: ApiBase.php:867
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:639
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:255
static int [][][] $filterIDsCache
Cache for self::filterIDs()
Definition: ApiBase.php:272
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:222
isReadMode()
Indicates whether this module requires read rights.
Definition: ApiBase.php:397
getErrorsByType( $type)
Returns a list of status messages of the given type.
getResult()
Get the result object.
Definition: ApiBase.php:625
Message subclass that prepends wikitext for API help.
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:427
getDescriptionMessage()
Return the description message.
Definition: ApiBase.php:2661
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2502
$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:339
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:1995
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php:269
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:2049
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1911
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1835
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1549
getMain()
Get the main module.
Definition: ApiBase.php:521
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:49
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
trackBlockNotices(array $errors)
Keep track of errors messages resulting from a block.
Definition: ApiBase.php:2135
getType()
Get the type of target for this particular block.
Definition: Block.php:1558
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition: ApiBase.php:266
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:253
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
checkTitleUserPermissions(Title $title, $actions, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2110
Exception used to abort API execution with an error.
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:653
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 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object '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:1276
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:91
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition: ApiBase.php:112
ApiMain $mMainModule
Definition: ApiBase.php:275
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition: ApiBase.php:142
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:553
This manages continuation state.
$value
getParent()
Get the parent of this module.
Definition: ApiBase.php:539
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2132
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1978
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:736
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:1990
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:278
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1602
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:158
validateUser( $value, $encParamName)
Validate and normalize parameters of type &#39;user&#39;.
Definition: ApiBase.php:1674
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true...
Definition: ApiBase.php:181
isDeprecated()
Indicates whether this module is deprecated.
Definition: ApiBase.php:429
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:363
string $mModuleName
Definition: ApiBase.php:277
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:461
IContextSource $context
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2204
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2256
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:850
const PARAM_ISMULTI_LIMIT1
(integer) Maximum number of values, for normal users.
Definition: ApiBase.php:209
static getBlockInfo(Block $block)
Get basic info about a given block.
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition: ApiBase.php:1768
const PARAM_MAX_CHARS
(integer) Maximum length of a string in characters (unicode codepoints).
Definition: ApiBase.php:228
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:47
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1241
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:132
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:1468
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2230
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:173
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:1073
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
static truncateArray(&$arr, $limit)
Truncate an array to a certain length.
Definition: ApiBase.php:2676
const PARAM_ISMULTI_LIMIT2
(integer) Maximum number of values, for users with the apihighimits right.
Definition: ApiBase.php:216
static sanitizeIP( $ip)
Convert an IP into a verbose, uppercase, normalized form.
Definition: IP.php:152
setOK( $ok)
Change operation status.
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:89
getHelpFlags()
Generates the list of flags for the help screen and for action=paraminfo.
Definition: ApiBase.php:2469
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:933
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE &#39;integer&#39;, enforce PARAM_MIN and PARAM_MAX?
Definition: ApiBase.php:118
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:2182
getModulePath()
Get the path to this module.
Definition: ApiBase.php:569
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:665
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:679
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:259
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition: ApiBase.php:1440
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:288
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is &#39;submodule&#39;, map parameter values to submodule paths.
Definition: ApiBase.php:166
getContext()
Get the base IContextSource object.
const IGNORE_USER_RIGHTS
Definition: User.php:77
$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:1995
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:439
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1114
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
isSitewide( $x=null)
Indicates that the block is a sitewide block.
Definition: Block.php:1145
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:935
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:703
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:505
$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:97
getModuleFromPath( $path)
Get a module from its module path.
Definition: ApiBase.php:587
string $mModulePrefix
Definition: ApiBase.php:277
const TYPE_AUTO
Definition: Block.php:99
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition: ApiBase.php:2591
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 probably a stub 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:905
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:125
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition: ApiBase.php:1793
const PARAM_SENSITIVE
(boolean) Is the parameter sensitive? Note &#39;password&#39;-type fields are always sensitive regardless of ...
Definition: ApiBase.php:194
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:257
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:246
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition: ApiBase.php:326
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1733
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:166
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:1008
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:714
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
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition: ApiBase.php:2002
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:420
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:490
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:1944
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:2165
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition: ApiBase.php:1849
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:513
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2076
const RE_IP_BYTE
Definition: IP.php:29
getDescription()
Returns the description string for this module.
Definition: ApiBase.php:2610
$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:2272
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1897
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:203
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:52
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:2194
getExamples()
Returns usage examples for this module.
Definition: ApiBase.php:2648
const ALL_DEFAULT_STRING
Definition: ApiBase.php:250
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module&#39;s error handler and die wit...
Definition: ApiBase.php:2018
$mParamCache
Definition: ApiBase.php:279
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:38
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:474
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is &#39;namespace&#39;, include these as additional possible values.
Definition: ApiBase.php:187
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:1639
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1718
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:106
array null bool $mModuleSource
Definition: ApiBase.php:281
const DB_REPLICA
Definition: defines.php:25
getParamDescription()
Returns an array of parameter descriptions.
Definition: ApiBase.php:2627
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:479
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:354
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:963
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:100
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:584
$ext
Definition: router.php:55
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:389
static getValidNamespaces()
Returns an array of the namespaces (by integer id) that exist on the wiki.
getExtendedDescription()
Return the extended help text message.
Definition: ApiBase.php:2243
Definition: Block.php:31
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:2625
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2301
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2092
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1957
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition: ApiBase.php:103
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1486
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
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:530
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:412
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:280
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:379