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 static $blockMsgMap = [
276  'blockedtext' => [ 'apierror-blocked', 'blocked' ],
277  'blockedtext-partial' => [ 'apierror-blocked', 'blocked' ],
278  'autoblockedtext' => [ 'apierror-autoblocked', 'autoblocked' ],
279  'systemblockedtext' => [ 'apierror-systemblocked', 'blocked' ],
280  ];
281 
283  private $mMainModule;
286  private $mReplicaDB = null;
287  private $mParamCache = [];
289  private $mModuleSource = false;
290 
296  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
297  $this->mMainModule = $mainModule;
298  $this->mModuleName = $moduleName;
299  $this->mModulePrefix = $modulePrefix;
300 
301  if ( !$this->isMain() ) {
302  $this->setContext( $mainModule->getContext() );
303  }
304  }
305 
306  /************************************************************************/
327  abstract public function execute();
328 
334  public function getModuleManager() {
335  return null;
336  }
337 
347  public function getCustomPrinter() {
348  return null;
349  }
350 
362  protected function getExamplesMessages() {
363  return [];
364  }
365 
371  public function getHelpUrls() {
372  return [];
373  }
374 
387  protected function getAllowedParams( /* $flags = 0 */ ) {
388  // int $flags is not declared because it causes "Strict standards"
389  // warning. Most derived classes do not implement it.
390  return [];
391  }
392 
397  public function shouldCheckMaxlag() {
398  return true;
399  }
400 
405  public function isReadMode() {
406  return true;
407  }
408 
420  public function isWriteMode() {
421  return false;
422  }
423 
428  public function mustBePosted() {
429  return $this->needsToken() !== false;
430  }
431 
437  public function isDeprecated() {
438  return false;
439  }
440 
447  public function isInternal() {
448  return false;
449  }
450 
469  public function needsToken() {
470  return false;
471  }
472 
482  protected function getWebUITokenSalt( array $params ) {
483  return null;
484  }
485 
498  public function getConditionalRequestData( $condition ) {
499  return null;
500  }
501 
504  /************************************************************************/
513  public function getModuleName() {
514  return $this->mModuleName;
515  }
516 
521  public function getModulePrefix() {
522  return $this->mModulePrefix;
523  }
524 
529  public function getMain() {
530  return $this->mMainModule;
531  }
532 
538  public function isMain() {
539  return $this === $this->mMainModule;
540  }
541 
547  public function getParent() {
548  return $this->isMain() ? null : $this->getMain();
549  }
550 
561  public function lacksSameOriginSecurity() {
562  // Main module has this method overridden
563  // Safety - avoid infinite loop:
564  if ( $this->isMain() ) {
565  self::dieDebug( __METHOD__, 'base method was called on main module.' );
566  }
567 
568  return $this->getMain()->lacksSameOriginSecurity();
569  }
570 
577  public function getModulePath() {
578  if ( $this->isMain() ) {
579  return 'main';
580  } elseif ( $this->getParent()->isMain() ) {
581  return $this->getModuleName();
582  } else {
583  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
584  }
585  }
586 
595  public function getModuleFromPath( $path ) {
596  $module = $this->getMain();
597  if ( $path === 'main' ) {
598  return $module;
599  }
600 
601  $parts = explode( '+', $path );
602  if ( count( $parts ) === 1 ) {
603  // In case the '+' was typed into URL, it resolves as a space
604  $parts = explode( ' ', $path );
605  }
606 
607  $count = count( $parts );
608  for ( $i = 0; $i < $count; $i++ ) {
609  $parent = $module;
610  $manager = $parent->getModuleManager();
611  if ( $manager === null ) {
612  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
613  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
614  }
615  $module = $manager->getModule( $parts[$i] );
616 
617  if ( $module === null ) {
618  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
619  $this->dieWithError(
620  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
621  'badmodule'
622  );
623  }
624  }
625 
626  return $module;
627  }
628 
633  public function getResult() {
634  // Main module has getResult() method overridden
635  // Safety - avoid infinite loop:
636  if ( $this->isMain() ) {
637  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
638  }
639 
640  return $this->getMain()->getResult();
641  }
642 
647  public function getErrorFormatter() {
648  // Main module has getErrorFormatter() method overridden
649  // Safety - avoid infinite loop:
650  if ( $this->isMain() ) {
651  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
652  }
653 
654  return $this->getMain()->getErrorFormatter();
655  }
656 
661  protected function getDB() {
662  if ( !isset( $this->mReplicaDB ) ) {
663  $this->mReplicaDB = wfGetDB( DB_REPLICA, 'api' );
664  }
665 
666  return $this->mReplicaDB;
667  }
668 
673  public function getContinuationManager() {
674  // Main module has getContinuationManager() method overridden
675  // Safety - avoid infinite loop:
676  if ( $this->isMain() ) {
677  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
678  }
679 
680  return $this->getMain()->getContinuationManager();
681  }
682 
687  public function setContinuationManager( ApiContinuationManager $manager = null ) {
688  // Main module has setContinuationManager() method overridden
689  // Safety - avoid infinite loop:
690  if ( $this->isMain() ) {
691  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
692  }
693 
694  $this->getMain()->setContinuationManager( $manager );
695  }
696 
699  /************************************************************************/
711  public function dynamicParameterDocumentation() {
712  return null;
713  }
714 
722  public function encodeParamName( $paramName ) {
723  if ( is_array( $paramName ) ) {
724  return array_map( function ( $name ) {
725  return $this->mModulePrefix . $name;
726  }, $paramName );
727  } else {
728  return $this->mModulePrefix . $paramName;
729  }
730  }
731 
744  public function extractRequestParams( $options = [] ) {
745  if ( is_bool( $options ) ) {
746  $options = [ 'parseLimit' => $options ];
747  }
748  $options += [
749  'parseLimit' => true,
750  'safeMode' => false,
751  ];
752 
753  $parseLimit = (bool)$options['parseLimit'];
754 
755  // Cache parameters, for performance and to avoid T26564.
756  if ( !isset( $this->mParamCache[$parseLimit] ) ) {
757  $params = $this->getFinalParams() ?: [];
758  $results = [];
759  $warned = [];
760 
761  // Process all non-templates and save templates for secondary
762  // processing.
763  $toProcess = [];
764  foreach ( $params as $paramName => $paramSettings ) {
765  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
766  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
767  } else {
768  try {
769  $results[$paramName] = $this->getParameterFromSettings(
770  $paramName, $paramSettings, $parseLimit
771  );
772  } catch ( ApiUsageException $ex ) {
773  $results[$paramName] = $ex;
774  }
775  }
776  }
777 
778  // Now process all the templates by successively replacing the
779  // placeholders with all client-supplied values.
780  // This bit duplicates JavaScript logic in
781  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
782  // If you update this, see if that needs updating too.
783  while ( $toProcess ) {
784  list( $name, $targets, $settings ) = array_shift( $toProcess );
785 
786  foreach ( $targets as $placeholder => $target ) {
787  if ( !array_key_exists( $target, $results ) ) {
788  // The target wasn't processed yet, try the next one.
789  // If all hit this case, the parameter has no expansions.
790  continue;
791  }
792  if ( !is_array( $results[$target] ) || !$results[$target] ) {
793  // The target was processed but has no (valid) values.
794  // That means it has no expansions.
795  break;
796  }
797 
798  // Expand this target in the name and all other targets,
799  // then requeue if there are more targets left or put in
800  // $results if all are done.
801  unset( $targets[$placeholder] );
802  $placeholder = '{' . $placeholder . '}';
803  // @phan-suppress-next-line PhanTypeNoAccessiblePropertiesForeach
804  foreach ( $results[$target] as $value ) {
805  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
806  // Skip values that make invalid parameter names.
807  $encTargetName = $this->encodeParamName( $target );
808  if ( !isset( $warned[$encTargetName][$value] ) ) {
809  $warned[$encTargetName][$value] = true;
810  $this->addWarning( [
811  'apiwarn-ignoring-invalid-templated-value',
812  wfEscapeWikiText( $encTargetName ),
813  wfEscapeWikiText( $value ),
814  ] );
815  }
816  continue;
817  }
818 
819  $newName = str_replace( $placeholder, $value, $name );
820  if ( !$targets ) {
821  try {
822  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
823  } catch ( ApiUsageException $ex ) {
824  $results[$newName] = $ex;
825  }
826  } else {
827  $newTargets = [];
828  foreach ( $targets as $k => $v ) {
829  $newTargets[$k] = str_replace( $placeholder, $value, $v );
830  }
831  $toProcess[] = [ $newName, $newTargets, $settings ];
832  }
833  }
834  break;
835  }
836  }
837 
838  $this->mParamCache[$parseLimit] = $results;
839  }
840 
841  $ret = $this->mParamCache[$parseLimit];
842  if ( !$options['safeMode'] ) {
843  foreach ( $ret as $v ) {
844  if ( $v instanceof ApiUsageException ) {
845  throw $v;
846  }
847  }
848  }
849 
850  return $this->mParamCache[$parseLimit];
851  }
852 
859  protected function getParameter( $paramName, $parseLimit = true ) {
860  $ret = $this->extractRequestParams( [
861  'parseLimit' => $parseLimit,
862  'safeMode' => true,
863  ] )[$paramName];
864  if ( $ret instanceof ApiUsageException ) {
865  throw $ret;
866  }
867  return $ret;
868  }
869 
876  public function requireOnlyOneParameter( $params, $required /*...*/ ) {
877  $required = func_get_args();
878  array_shift( $required );
879 
880  $intersection = array_intersect( array_keys( array_filter( $params,
881  [ $this, 'parameterNotEmpty' ] ) ), $required );
882 
883  if ( count( $intersection ) > 1 ) {
884  $this->dieWithError( [
885  'apierror-invalidparammix',
886  Message::listParam( array_map(
887  function ( $p ) {
888  return '<var>' . $this->encodeParamName( $p ) . '</var>';
889  },
890  array_values( $intersection )
891  ) ),
892  count( $intersection ),
893  ] );
894  } elseif ( count( $intersection ) == 0 ) {
895  $this->dieWithError( [
896  'apierror-missingparam-one-of',
897  Message::listParam( array_map(
898  function ( $p ) {
899  return '<var>' . $this->encodeParamName( $p ) . '</var>';
900  },
901  array_values( $required )
902  ) ),
903  count( $required ),
904  ], 'missingparam' );
905  }
906  }
907 
914  public function requireMaxOneParameter( $params, $required /*...*/ ) {
915  $required = func_get_args();
916  array_shift( $required );
917 
918  $intersection = array_intersect( array_keys( array_filter( $params,
919  [ $this, 'parameterNotEmpty' ] ) ), $required );
920 
921  if ( count( $intersection ) > 1 ) {
922  $this->dieWithError( [
923  'apierror-invalidparammix',
924  Message::listParam( array_map(
925  function ( $p ) {
926  return '<var>' . $this->encodeParamName( $p ) . '</var>';
927  },
928  array_values( $intersection )
929  ) ),
930  count( $intersection ),
931  ] );
932  }
933  }
934 
942  public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
943  $required = func_get_args();
944  array_shift( $required );
945 
946  $intersection = array_intersect(
947  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
948  $required
949  );
950 
951  if ( count( $intersection ) == 0 ) {
952  $this->dieWithError( [
953  'apierror-missingparam-at-least-one-of',
954  Message::listParam( array_map(
955  function ( $p ) {
956  return '<var>' . $this->encodeParamName( $p ) . '</var>';
957  },
958  array_values( $required )
959  ) ),
960  count( $required ),
961  ], 'missingparam' );
962  }
963  }
964 
972  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
973  // Skip if $wgDebugAPI is set or we're in internal mode
974  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
975  return;
976  }
977 
978  $queryValues = $this->getRequest()->getQueryValues();
979  $badParams = [];
980  foreach ( $params as $param ) {
981  if ( $prefix !== 'noprefix' ) {
982  $param = $this->encodeParamName( $param );
983  }
984  if ( array_key_exists( $param, $queryValues ) ) {
985  $badParams[] = $param;
986  }
987  }
988 
989  if ( $badParams ) {
990  $this->dieWithError(
991  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
992  );
993  }
994  }
995 
1002  private function parameterNotEmpty( $x ) {
1003  return !is_null( $x ) && $x !== false;
1004  }
1005 
1017  public function getTitleOrPageId( $params, $load = false ) {
1018  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1019 
1020  $pageObj = null;
1021  if ( isset( $params['title'] ) ) {
1022  $titleObj = Title::newFromText( $params['title'] );
1023  if ( !$titleObj || $titleObj->isExternal() ) {
1024  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1025  }
1026  if ( !$titleObj->canExist() ) {
1027  $this->dieWithError( 'apierror-pagecannotexist' );
1028  }
1029  $pageObj = WikiPage::factory( $titleObj );
1030  if ( $load !== false ) {
1031  $pageObj->loadPageData( $load );
1032  }
1033  } elseif ( isset( $params['pageid'] ) ) {
1034  if ( $load === false ) {
1035  $load = 'fromdb';
1036  }
1037  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1038  if ( !$pageObj ) {
1039  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1040  }
1041  }
1042 
1043  return $pageObj;
1044  }
1045 
1054  public function getTitleFromTitleOrPageId( $params ) {
1055  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1056 
1057  $titleObj = null;
1058  if ( isset( $params['title'] ) ) {
1059  $titleObj = Title::newFromText( $params['title'] );
1060  if ( !$titleObj || $titleObj->isExternal() ) {
1061  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1062  }
1063  return $titleObj;
1064  } elseif ( isset( $params['pageid'] ) ) {
1065  $titleObj = Title::newFromID( $params['pageid'] );
1066  if ( !$titleObj ) {
1067  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1068  }
1069  }
1070 
1071  return $titleObj;
1072  }
1073 
1082  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1083  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1084 
1085  switch ( $watchlist ) {
1086  case 'watch':
1087  return true;
1088 
1089  case 'unwatch':
1090  return false;
1091 
1092  case 'preferences':
1093  # If the user is already watching, don't bother checking
1094  if ( $userWatching ) {
1095  return true;
1096  }
1097  # If no user option was passed, use watchdefault and watchcreations
1098  if ( is_null( $userOption ) ) {
1099  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1100  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1101  }
1102 
1103  # Watch the article based on the user preference
1104  return $this->getUser()->getBoolOption( $userOption );
1105 
1106  case 'nochange':
1107  return $userWatching;
1108 
1109  default:
1110  return $userWatching;
1111  }
1112  }
1113 
1123  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1124  // Some classes may decide to change parameter names
1125  $encParamName = $this->encodeParamName( $paramName );
1126 
1127  // Shorthand
1128  if ( !is_array( $paramSettings ) ) {
1129  $paramSettings = [
1130  self::PARAM_DFLT => $paramSettings,
1131  ];
1132  }
1133 
1134  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1135  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1136  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1137  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1138  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1139  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1140  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1141  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1142  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1143  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1144 
1145  // When type is not given, and no choices, the type is the same as $default
1146  if ( !isset( $type ) ) {
1147  if ( isset( $default ) ) {
1148  $type = gettype( $default );
1149  } else {
1150  $type = 'NULL'; // allow everything
1151  }
1152  }
1153 
1154  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1155  $this->getMain()->markParamsSensitive( $encParamName );
1156  }
1157 
1158  if ( $type == 'boolean' ) {
1159  if ( isset( $default ) && $default !== false ) {
1160  // Having a default value of anything other than 'false' is not allowed
1161  self::dieDebug(
1162  __METHOD__,
1163  "Boolean param $encParamName's default is set to '$default'. " .
1164  'Boolean parameters must default to false.'
1165  );
1166  }
1167 
1168  $value = $this->getMain()->getCheck( $encParamName );
1169  $provided = $value;
1170  } elseif ( $type == 'upload' ) {
1171  if ( isset( $default ) ) {
1172  // Having a default value is not allowed
1173  self::dieDebug(
1174  __METHOD__,
1175  "File upload param $encParamName's default is set to " .
1176  "'$default'. File upload parameters may not have a default." );
1177  }
1178  if ( $multi ) {
1179  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1180  }
1181  $value = $this->getMain()->getUpload( $encParamName );
1182  $provided = $value->exists();
1183  if ( !$value->exists() ) {
1184  // This will get the value without trying to normalize it
1185  // (because trying to normalize a large binary file
1186  // accidentally uploaded as a field fails spectacularly)
1187  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1188  if ( $value !== null ) {
1189  $this->dieWithError(
1190  [ 'apierror-badupload', $encParamName ],
1191  "badupload_{$encParamName}"
1192  );
1193  }
1194  }
1195  } else {
1196  $value = $this->getMain()->getVal( $encParamName, $default );
1197  $provided = $this->getMain()->getCheck( $encParamName );
1198 
1199  if ( isset( $value ) && $type == 'namespace' ) {
1201  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1202  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1203  ) {
1204  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1205  }
1206  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1207  // specify all namespaces irrespective of PARAM_ALL.
1208  $allowAll = true;
1209  }
1210  if ( isset( $value ) && $type == 'submodule' ) {
1211  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1212  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1213  } else {
1214  $type = $this->getModuleManager()->getNames( $paramName );
1215  }
1216  }
1217 
1218  $request = $this->getMain()->getRequest();
1219  $rawValue = $request->getRawVal( $encParamName );
1220  if ( $rawValue === null ) {
1221  $rawValue = $default;
1222  }
1223 
1224  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1225  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1226  if ( $multi ) {
1227  // This loses the potential checkTitleEncoding() transformation done by
1228  // WebRequest for $_GET. Let's call that a feature.
1229  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1230  } else {
1231  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1232  }
1233  }
1234 
1235  // Check for NFC normalization, and warn
1236  if ( $rawValue !== $value ) {
1237  $this->handleParamNormalization( $paramName, $value, $rawValue );
1238  }
1239  }
1240 
1241  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1242  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1243  self::dieDebug(
1244  __METHOD__,
1245  "For param $encParamName, PARAM_ALL collides with a possible value" );
1246  }
1247  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1248  $value = $this->parseMultiValue(
1249  $encParamName,
1250  $value,
1251  $multi,
1252  is_array( $type ) ? $type : null,
1253  $allowAll ? $allSpecifier : null,
1254  $multiLimit1,
1255  $multiLimit2
1256  );
1257  }
1258 
1259  if ( isset( $value ) ) {
1260  // More validation only when choices were not given
1261  // choices were validated in parseMultiValue()
1262  if ( !is_array( $type ) ) {
1263  switch ( $type ) {
1264  case 'NULL': // nothing to do
1265  break;
1266  case 'string':
1267  case 'text':
1268  case 'password':
1269  if ( $required && $value === '' ) {
1270  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1271  }
1272  break;
1273  case 'integer': // Force everything using intval() and optionally validate limits
1274  $min = $paramSettings[self::PARAM_MIN] ?? null;
1275  $max = $paramSettings[self::PARAM_MAX] ?? null;
1276  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1277 
1278  if ( is_array( $value ) ) {
1279  $value = array_map( 'intval', $value );
1280  if ( !is_null( $min ) || !is_null( $max ) ) {
1281  foreach ( $value as &$v ) {
1282  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1283  }
1284  }
1285  } else {
1286  $value = (int)$value;
1287  if ( !is_null( $min ) || !is_null( $max ) ) {
1288  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1289  }
1290  }
1291  break;
1292  case 'limit':
1293  if ( !$parseLimit ) {
1294  // Don't do any validation whatsoever
1295  break;
1296  }
1297  if ( !isset( $paramSettings[self::PARAM_MAX] )
1298  || !isset( $paramSettings[self::PARAM_MAX2] )
1299  ) {
1300  self::dieDebug(
1301  __METHOD__,
1302  "MAX1 or MAX2 are not defined for the limit $encParamName"
1303  );
1304  }
1305  if ( $multi ) {
1306  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1307  }
1308  $min = $paramSettings[self::PARAM_MIN] ?? 0;
1309  if ( $value == 'max' ) {
1310  $value = $this->getMain()->canApiHighLimits()
1311  ? $paramSettings[self::PARAM_MAX2]
1312  : $paramSettings[self::PARAM_MAX];
1313  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1314  } else {
1315  $value = (int)$value;
1316  $this->validateLimit(
1317  $paramName,
1318  $value,
1319  $min,
1320  $paramSettings[self::PARAM_MAX],
1321  $paramSettings[self::PARAM_MAX2]
1322  );
1323  }
1324  break;
1325  case 'boolean':
1326  if ( $multi ) {
1327  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1328  }
1329  break;
1330  case 'timestamp':
1331  if ( is_array( $value ) ) {
1332  foreach ( $value as $key => $val ) {
1333  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1334  }
1335  } else {
1336  $value = $this->validateTimestamp( $value, $encParamName );
1337  }
1338  break;
1339  case 'user':
1340  if ( is_array( $value ) ) {
1341  foreach ( $value as $key => $val ) {
1342  $value[$key] = $this->validateUser( $val, $encParamName );
1343  }
1344  } else {
1345  $value = $this->validateUser( $value, $encParamName );
1346  }
1347  break;
1348  case 'upload': // nothing to do
1349  break;
1350  case 'tags':
1351  // If change tagging was requested, check that the tags are valid.
1352  if ( !is_array( $value ) && !$multi ) {
1353  $value = [ $value ];
1354  }
1356  if ( !$tagsStatus->isGood() ) {
1357  $this->dieStatus( $tagsStatus );
1358  }
1359  break;
1360  default:
1361  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1362  }
1363  }
1364 
1365  // Throw out duplicates if requested
1366  if ( !$dupes && is_array( $value ) ) {
1367  $value = array_unique( $value );
1368  }
1369 
1370  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1371  foreach ( (array)$value as $val ) {
1372  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1373  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1374  ) {
1375  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1376  $paramSettings[self::PARAM_MAX_BYTES] ] );
1377  }
1378  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1379  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1380  ) {
1381  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1382  $paramSettings[self::PARAM_MAX_CHARS] ] );
1383  }
1384  }
1385  }
1386 
1387  // Set a warning if a deprecated parameter has been passed
1388  if ( $deprecated && $provided ) {
1389  $feature = $encParamName;
1390  $m = $this;
1391  while ( !$m->isMain() ) {
1392  $p = $m->getParent();
1393  $name = $m->getModuleName();
1394  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1395  $feature = "{$param}={$name}&{$feature}";
1396  $m = $p;
1397  }
1398  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1399  }
1400 
1401  // Set a warning if a deprecated parameter value has been passed
1402  $usedDeprecatedValues = $deprecatedValues && $provided
1403  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1404  : [];
1405  if ( $usedDeprecatedValues ) {
1406  $feature = "$encParamName=";
1407  $m = $this;
1408  while ( !$m->isMain() ) {
1409  $p = $m->getParent();
1410  $name = $m->getModuleName();
1411  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1412  $feature = "{$param}={$name}&{$feature}";
1413  $m = $p;
1414  }
1415  foreach ( $usedDeprecatedValues as $v ) {
1416  $msg = $deprecatedValues[$v];
1417  if ( $msg === true ) {
1418  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1419  }
1420  $this->addDeprecation( $msg, "$feature$v" );
1421  }
1422  }
1423  } elseif ( $required ) {
1424  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1425  }
1426 
1427  return $value;
1428  }
1429 
1437  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1438  $encParamName = $this->encodeParamName( $paramName );
1439  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1440  }
1441 
1449  protected function explodeMultiValue( $value, $limit ) {
1450  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1451  $sep = "\x1f";
1452  $value = substr( $value, 1 );
1453  } else {
1454  $sep = '|';
1455  }
1456 
1457  return explode( $sep, $value, $limit );
1458  }
1459 
1477  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1478  $allSpecifier = null, $limit1 = null, $limit2 = null
1479  ) {
1480  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1481  return [];
1482  }
1483  $limit1 = $limit1 ?: self::LIMIT_SML1;
1484  $limit2 = $limit2 ?: self::LIMIT_SML2;
1485 
1486  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1487  // because it unstubs $wgUser
1488  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1489  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1490  ? $limit2
1491  : $limit1;
1492 
1493  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1494  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1495  ) {
1496  return $allowedValues;
1497  }
1498 
1499  if ( count( $valuesList ) > $sizeLimit ) {
1500  $this->dieWithError(
1501  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1502  "too-many-$valueName"
1503  );
1504  }
1505 
1506  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1507  // T35482 - Allow entries with | in them for non-multiple values
1508  if ( in_array( $value, $allowedValues, true ) ) {
1509  return $value;
1510  }
1511 
1512  $values = array_map( function ( $v ) {
1513  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1514  }, $allowedValues );
1515  $this->dieWithError( [
1516  'apierror-multival-only-one-of',
1517  $valueName,
1518  Message::listParam( $values ),
1519  count( $values ),
1520  ], "multival_$valueName" );
1521  }
1522 
1523  if ( is_array( $allowedValues ) ) {
1524  // Check for unknown values
1525  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1526  if ( count( $unknown ) ) {
1527  if ( $allowMultiple ) {
1528  $this->addWarning( [
1529  'apiwarn-unrecognizedvalues',
1530  $valueName,
1531  Message::listParam( $unknown, 'comma' ),
1532  count( $unknown ),
1533  ] );
1534  } else {
1535  $this->dieWithError(
1536  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1537  "unknown_$valueName"
1538  );
1539  }
1540  }
1541  // Now throw them out
1542  $valuesList = array_intersect( $valuesList, $allowedValues );
1543  }
1544 
1545  return $allowMultiple ? $valuesList : $valuesList[0];
1546  }
1547 
1558  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1559  $enforceLimits = false
1560  ) {
1561  if ( !is_null( $min ) && $value < $min ) {
1562  $msg = ApiMessage::create(
1563  [ 'apierror-integeroutofrange-belowminimum',
1564  $this->encodeParamName( $paramName ), $min, $value ],
1565  'integeroutofrange',
1566  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1567  );
1568  $this->warnOrDie( $msg, $enforceLimits );
1569  $value = $min;
1570  }
1571 
1572  // Minimum is always validated, whereas maximum is checked only if not
1573  // running in internal call mode
1574  if ( $this->getMain()->isInternalMode() ) {
1575  return;
1576  }
1577 
1578  // Optimization: do not check user's bot status unless really needed -- skips db query
1579  // assumes $botMax >= $max
1580  if ( !is_null( $max ) && $value > $max ) {
1581  if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1582  if ( $value > $botMax ) {
1583  $msg = ApiMessage::create(
1584  [ 'apierror-integeroutofrange-abovebotmax',
1585  $this->encodeParamName( $paramName ), $botMax, $value ],
1586  'integeroutofrange',
1587  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1588  );
1589  $this->warnOrDie( $msg, $enforceLimits );
1590  $value = $botMax;
1591  }
1592  } else {
1593  $msg = ApiMessage::create(
1594  [ 'apierror-integeroutofrange-abovemax',
1595  $this->encodeParamName( $paramName ), $max, $value ],
1596  'integeroutofrange',
1597  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1598  );
1599  $this->warnOrDie( $msg, $enforceLimits );
1600  $value = $max;
1601  }
1602  }
1603  }
1604 
1611  protected function validateTimestamp( $value, $encParamName ) {
1612  // Confusing synonyms for the current time accepted by wfTimestamp()
1613  // (wfTimestamp() also accepts various non-strings and the string of 14
1614  // ASCII NUL bytes, but those can't get here)
1615  if ( !$value ) {
1616  $this->addDeprecation(
1617  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1618  'unclear-"now"-timestamp'
1619  );
1620  return wfTimestamp( TS_MW );
1621  }
1622 
1623  // Explicit synonym for the current time
1624  if ( $value === 'now' ) {
1625  return wfTimestamp( TS_MW );
1626  }
1627 
1628  $timestamp = wfTimestamp( TS_MW, $value );
1629  if ( $timestamp === false ) {
1630  $this->dieWithError(
1631  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1632  "badtimestamp_{$encParamName}"
1633  );
1634  }
1635 
1636  return $timestamp;
1637  }
1638 
1648  final public function validateToken( $token, array $params ) {
1649  $tokenType = $this->needsToken();
1651  if ( !isset( $salts[$tokenType] ) ) {
1652  throw new MWException(
1653  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1654  'without registering it'
1655  );
1656  }
1657 
1658  $tokenObj = ApiQueryTokens::getToken(
1659  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1660  );
1661  if ( $tokenObj->match( $token ) ) {
1662  return true;
1663  }
1664 
1665  $webUiSalt = $this->getWebUITokenSalt( $params );
1666  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1667  $token,
1668  $webUiSalt,
1669  $this->getRequest()
1670  ) ) {
1671  return true;
1672  }
1673 
1674  return false;
1675  }
1676 
1683  private function validateUser( $value, $encParamName ) {
1685  return $value;
1686  }
1687 
1688  $name = User::getCanonicalName( $value, 'valid' );
1689  if ( $name !== false ) {
1690  return $name;
1691  }
1692 
1693  if (
1694  // We allow ranges as well, for blocks.
1695  IP::isIPAddress( $value ) ||
1696  // See comment for User::isIP. We don't just call that function
1697  // here because it also returns true for things like
1698  // 300.300.300.300 that are neither valid usernames nor valid IP
1699  // addresses.
1700  preg_match(
1701  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1702  $value
1703  )
1704  ) {
1705  return IP::sanitizeIP( $value );
1706  }
1707 
1708  $this->dieWithError(
1709  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1710  "baduser_{$encParamName}"
1711  );
1712  }
1713 
1716  /************************************************************************/
1727  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1728  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1729  if ( $value === null ) {
1730  return;
1731  }
1732 
1733  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1734  }
1735 
1742  public function getWatchlistUser( $params ) {
1743  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1744  $user = User::newFromName( $params['owner'], false );
1745  if ( !( $user && $user->getId() ) ) {
1746  $this->dieWithError(
1747  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1748  );
1749  }
1750  $token = $user->getOption( 'watchlisttoken' );
1751  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1752  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1753  }
1754  } else {
1755  if ( !$this->getUser()->isLoggedIn() ) {
1756  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1757  }
1758  $this->checkUserRightsAny( 'viewmywatchlist' );
1759  $user = $this->getUser();
1760  }
1761 
1762  return $user;
1763  }
1764 
1777  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1778  if ( is_string( $msg ) ) {
1779  $msg = wfMessage( $msg );
1780  } elseif ( is_array( $msg ) ) {
1781  $msg = wfMessage( ...$msg );
1782  }
1783  if ( !$msg instanceof Message ) {
1784  return null;
1785  }
1786 
1787  $msg->setContext( $context );
1788  if ( $params ) {
1789  $msg->params( $params );
1790  }
1791 
1792  return $msg;
1793  }
1794 
1802  public function errorArrayToStatus( array $errors, User $user = null ) {
1803  if ( $user === null ) {
1804  $user = $this->getUser();
1805  }
1806 
1808  foreach ( $errors as $error ) {
1809  if ( !is_array( $error ) ) {
1810  $error = [ $error ];
1811  }
1812  if ( is_string( $error[0] ) && isset( self::$blockMsgMap[$error[0]] ) && $user->getBlock() ) {
1813  list( $msg, $code ) = self::$blockMsgMap[$error[0]];
1814  $status->fatal( ApiMessage::create( $msg, $code,
1815  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1816  ) );
1817  } else {
1818  $status->fatal( ...$error );
1819  }
1820  }
1821  return $status;
1822  }
1823 
1831  if ( $user === null ) {
1832  $user = $this->getUser();
1833  }
1834 
1835  foreach ( self::$blockMsgMap as $msg => list( $apiMsg, $code ) ) {
1836  if ( $status->hasMessage( $msg ) && $user->getBlock() ) {
1837  $status->replaceMessage( $msg, ApiMessage::create( $apiMsg, $code,
1838  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1839  ) );
1840  }
1841  }
1842  }
1843 
1848  protected function useTransactionalTimeLimit() {
1849  if ( $this->getRequest()->wasPosted() ) {
1851  }
1852  }
1853 
1862  protected function filterIDs( $fields, array $ids ) {
1863  $min = INF;
1864  $max = 0;
1865  foreach ( $fields as list( $table, $field ) ) {
1866  if ( isset( self::$filterIDsCache[$table][$field] ) ) {
1867  $row = self::$filterIDsCache[$table][$field];
1868  } else {
1869  $row = $this->getDB()->selectRow(
1870  $table,
1871  [
1872  'min_id' => "MIN($field)",
1873  'max_id' => "MAX($field)",
1874  ],
1875  '',
1876  __METHOD__
1877  );
1878  self::$filterIDsCache[$table][$field] = $row;
1879  }
1880  $min = min( $min, $row->min_id );
1881  $max = max( $max, $row->max_id );
1882  }
1883  return array_filter( $ids, function ( $id ) use ( $min, $max ) {
1884  return ( is_int( $id ) && $id >= 0 || ctype_digit( $id ) )
1885  && $id >= $min && $id <= $max;
1886  } );
1887  }
1888 
1891  /************************************************************************/
1910  public function addWarning( $msg, $code = null, $data = null ) {
1911  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1912  }
1913 
1924  public function addDeprecation( $msg, $feature, $data = [] ) {
1925  $data = (array)$data;
1926  if ( $feature !== null ) {
1927  $data['feature'] = $feature;
1928  $this->logFeatureUsage( $feature );
1929  }
1930  $this->addWarning( $msg, 'deprecation', $data );
1931 
1932  // No real need to deduplicate here, ApiErrorFormatter does that for
1933  // us (assuming the hook is deterministic).
1934  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1935  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1936  if ( count( $msgs ) > 1 ) {
1937  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1938  $msg = ( new RawMessage( $key ) )->params( $msgs );
1939  } else {
1940  $msg = reset( $msgs );
1941  }
1942  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1943  }
1944 
1957  public function addError( $msg, $code = null, $data = null ) {
1958  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1959  }
1960 
1970  public function addMessagesFromStatus(
1971  StatusValue $status, $types = [ 'warning', 'error' ], array $filter = []
1972  ) {
1973  $this->getErrorFormatter()->addMessagesFromStatus(
1974  $this->getModulePath(), $status, $types, $filter
1975  );
1976  }
1977 
1991  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
1992  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
1993  }
1994 
2003  public function dieWithException( $exception, array $options = [] ) {
2004  $this->dieWithError(
2005  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
2006  );
2007  }
2008 
2015  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
2016  if ( $enforceLimits ) {
2017  $this->dieWithError( $msg );
2018  } else {
2019  $this->addWarning( $msg );
2020  }
2021  }
2022 
2031  public function dieBlocked( Block $block ) {
2032  // Die using the appropriate message depending on block type
2033  if ( $block->getType() == Block::TYPE_AUTO ) {
2034  $this->dieWithError(
2035  'apierror-autoblocked',
2036  'autoblocked',
2037  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2038  );
2039  } elseif ( !$block->isSitewide() ) {
2040  $this->dieWithError(
2041  'apierror-blocked-partial',
2042  'blocked',
2043  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2044  );
2045  } else {
2046  $this->dieWithError(
2047  'apierror-blocked',
2048  'blocked',
2049  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2050  );
2051  }
2052  }
2053 
2062  public function dieStatus( StatusValue $status ) {
2063  if ( $status->isGood() ) {
2064  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2065  }
2066 
2067  // ApiUsageException needs a fatal status, but this method has
2068  // historically accepted any non-good status. Convert it if necessary.
2069  $status->setOK( false );
2070  if ( !$status->getErrorsByType( 'error' ) ) {
2071  $newStatus = Status::newGood();
2072  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2073  $newStatus->fatal( $err['message'], ...$err['params'] );
2074  }
2075  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2076  $newStatus->fatal( 'unknownerror-nocode' );
2077  }
2078  $status = $newStatus;
2079  }
2080 
2081  $this->addBlockInfoToStatus( $status );
2082  throw new ApiUsageException( $this, $status );
2083  }
2084 
2090  public function dieReadOnly() {
2091  $this->dieWithError(
2092  'apierror-readonly',
2093  'readonly',
2094  [ 'readonlyreason' => wfReadOnlyReason() ]
2095  );
2096  }
2097 
2106  public function checkUserRightsAny( $rights, $user = null ) {
2107  if ( !$user ) {
2108  $user = $this->getUser();
2109  }
2110  $rights = (array)$rights;
2111  if ( !$user->isAllowedAny( ...$rights ) ) {
2112  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2113  }
2114  }
2115 
2128  public function checkTitleUserPermissions( Title $title, $actions, $options = [] ) {
2129  if ( !is_array( $options ) ) {
2130  wfDeprecated( '$user as the third parameter to ' . __METHOD__, '1.33' );
2131  $options = [ 'user' => $options ];
2132  }
2133  $user = $options['user'] ?? $this->getUser();
2134 
2135  $errors = [];
2136  foreach ( (array)$actions as $action ) {
2137  $errors = array_merge( $errors, $title->getUserPermissionsErrors( $action, $user ) );
2138  }
2139 
2140  if ( $errors ) {
2141  // track block notices
2142  if ( $this->getConfig()->get( 'EnableBlockNoticeStats' ) ) {
2143  $this->trackBlockNotices( $errors );
2144  }
2145 
2146  if ( !empty( $options['autoblock'] ) ) {
2147  $user->spreadAnyEditBlock();
2148  }
2149 
2150  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2151  }
2152  }
2153 
2159  private function trackBlockNotices( array $errors ) {
2160  $errorMessageKeys = [
2161  'blockedtext',
2162  'blockedtext-partial',
2163  'autoblockedtext',
2164  'systemblockedtext',
2165  ];
2166 
2167  $statsd = MediaWikiServices::getInstance()->getStatsdDataFactory();
2168 
2169  foreach ( $errors as $error ) {
2170  if ( in_array( $error[0], $errorMessageKeys ) ) {
2171  $wiki = $this->getConfig()->get( 'DBname' );
2172  $statsd->increment( 'BlockNotices.' . $wiki . '.MediaWikiApi.returned' );
2173  break;
2174  }
2175  }
2176  }
2177 
2189  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2190  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2191  $this->dieWithError( $msg, $code, $data, $httpCode );
2192  } else {
2193  $this->addWarning( $msg, $code, $data );
2194  }
2195  }
2196 
2206  protected function dieContinueUsageIf( $condition ) {
2207  if ( $condition ) {
2208  $this->dieWithError( 'apierror-badcontinue' );
2209  }
2210  }
2211 
2218  protected static function dieDebug( $method, $message ) {
2219  throw new MWException( "Internal error in $method: $message" );
2220  }
2221 
2228  public function logFeatureUsage( $feature ) {
2229  static $loggedFeatures = [];
2230 
2231  // Only log each feature once per request. We can get multiple calls from calls to
2232  // extractRequestParams() with different values for 'parseLimit', for example.
2233  if ( isset( $loggedFeatures[$feature] ) ) {
2234  return;
2235  }
2236  $loggedFeatures[$feature] = true;
2237 
2238  $request = $this->getRequest();
2239  $ctx = [
2240  'feature' => $feature,
2241  // Spaces to underscores in 'username' for historical reasons.
2242  'username' => str_replace( ' ', '_', $this->getUser()->getName() ),
2243  'ip' => $request->getIP(),
2244  'referer' => (string)$request->getHeader( 'Referer' ),
2245  'agent' => $this->getMain()->getUserAgent(),
2246  ];
2247 
2248  // Text string is deprecated. Remove (or replace with just $feature) in MW 1.34.
2249  $s = '"' . addslashes( $ctx['feature'] ) . '"' .
2250  ' "' . wfUrlencode( $ctx['username'] ) . '"' .
2251  ' "' . $ctx['ip'] . '"' .
2252  ' "' . addslashes( $ctx['referer'] ) . '"' .
2253  ' "' . addslashes( $ctx['agent'] ) . '"';
2254 
2255  wfDebugLog( 'api-feature-usage', $s, 'private', $ctx );
2256  }
2257 
2260  /************************************************************************/
2274  protected function getSummaryMessage() {
2275  return "apihelp-{$this->getModulePath()}-summary";
2276  }
2277 
2287  protected function getExtendedDescription() {
2288  return [ [
2289  "apihelp-{$this->getModulePath()}-extended-description",
2290  'api-help-no-extended-description',
2291  ] ];
2292  }
2293 
2300  public function getFinalSummary() {
2301  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2302  $this->getModulePrefix(),
2303  $this->getModuleName(),
2304  $this->getModulePath(),
2305  ] );
2306  return $msg;
2307  }
2308 
2316  public function getFinalDescription() {
2317  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2318  $this->getModulePrefix(),
2319  $this->getModuleName(),
2320  $this->getModulePath(),
2321  ] );
2322  $extendedDescription = self::makeMessage(
2323  $this->getExtendedDescription(), $this->getContext(), [
2324  $this->getModulePrefix(),
2325  $this->getModuleName(),
2326  $this->getModulePath(),
2327  ]
2328  );
2329 
2330  $msgs = [ $summary, $extendedDescription ];
2331 
2332  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2333 
2334  return $msgs;
2335  }
2336 
2345  public function getFinalParams( $flags = 0 ) {
2346  $params = $this->getAllowedParams( $flags );
2347  if ( !$params ) {
2348  $params = [];
2349  }
2350 
2351  if ( $this->needsToken() ) {
2352  $params['token'] = [
2353  self::PARAM_TYPE => 'string',
2354  self::PARAM_REQUIRED => true,
2355  self::PARAM_SENSITIVE => true,
2356  self::PARAM_HELP_MSG => [
2357  'api-help-param-token',
2358  $this->needsToken(),
2359  ],
2360  ] + ( $params['token'] ?? [] );
2361  }
2362 
2363  // Avoid PHP 7.1 warning of passing $this by reference
2364  $apiModule = $this;
2365  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2366 
2367  return $params;
2368  }
2369 
2377  public function getFinalParamDescription() {
2378  $prefix = $this->getModulePrefix();
2379  $name = $this->getModuleName();
2380  $path = $this->getModulePath();
2381 
2382  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2383  $msgs = [];
2384  foreach ( $params as $param => $settings ) {
2385  if ( !is_array( $settings ) ) {
2386  $settings = [];
2387  }
2388 
2389  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2390  $msg = $settings[self::PARAM_HELP_MSG];
2391  } else {
2392  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2393  }
2394  $msg = self::makeMessage( $msg, $this->getContext(),
2395  [ $prefix, $param, $name, $path ] );
2396  if ( !$msg ) {
2397  self::dieDebug( __METHOD__,
2398  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2399  }
2400  $msgs[$param] = [ $msg ];
2401 
2402  if ( isset( $settings[self::PARAM_TYPE] ) &&
2403  $settings[self::PARAM_TYPE] === 'submodule'
2404  ) {
2405  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2406  $map = $settings[self::PARAM_SUBMODULE_MAP];
2407  } else {
2408  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2409  $map = [];
2410  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2411  $map[$submoduleName] = $prefix . $submoduleName;
2412  }
2413  }
2414  ksort( $map );
2415  $submodules = [];
2416  $deprecatedSubmodules = [];
2417  foreach ( $map as $v => $m ) {
2418  $arr = &$submodules;
2419  $isDeprecated = false;
2420  $summary = null;
2421  try {
2422  $submod = $this->getModuleFromPath( $m );
2423  if ( $submod ) {
2424  $summary = $submod->getFinalSummary();
2425  $isDeprecated = $submod->isDeprecated();
2426  if ( $isDeprecated ) {
2427  $arr = &$deprecatedSubmodules;
2428  }
2429  }
2430  } catch ( ApiUsageException $ex ) {
2431  // Ignore
2432  }
2433  if ( $summary ) {
2434  $key = $summary->getKey();
2435  $params = $summary->getParams();
2436  } else {
2437  $key = 'api-help-undocumented-module';
2438  $params = [ $m ];
2439  }
2440  $m = new ApiHelpParamValueMessage( "[[Special:ApiHelp/$m|$v]]", $key, $params, $isDeprecated );
2441  $arr[] = $m->setContext( $this->getContext() );
2442  }
2443  $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
2444  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2445  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2446  self::dieDebug( __METHOD__,
2447  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2448  }
2449  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2450  self::dieDebug( __METHOD__,
2451  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2452  'ApiBase::PARAM_TYPE is an array' );
2453  }
2454 
2455  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2456  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2457 
2458  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2459  if ( isset( $valueMsgs[$value] ) ) {
2460  $msg = $valueMsgs[$value];
2461  } else {
2462  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2463  }
2464  $m = self::makeMessage( $msg, $this->getContext(),
2465  [ $prefix, $param, $name, $path, $value ] );
2466  if ( $m ) {
2467  $m = new ApiHelpParamValueMessage(
2468  $value,
2469  [ $m->getKey(), 'api-help-param-no-description' ],
2470  $m->getParams(),
2471  isset( $deprecatedValues[$value] )
2472  );
2473  $msgs[$param][] = $m->setContext( $this->getContext() );
2474  } else {
2475  self::dieDebug( __METHOD__,
2476  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2477  }
2478  }
2479  }
2480 
2481  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2482  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2483  self::dieDebug( __METHOD__,
2484  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2485  }
2486  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2487  $m = self::makeMessage( $m, $this->getContext(),
2488  [ $prefix, $param, $name, $path ] );
2489  if ( $m ) {
2490  $msgs[$param][] = $m;
2491  } else {
2492  self::dieDebug( __METHOD__,
2493  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2494  }
2495  }
2496  }
2497  }
2498 
2499  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2500 
2501  return $msgs;
2502  }
2503 
2513  protected function getHelpFlags() {
2514  $flags = [];
2515 
2516  if ( $this->isDeprecated() ) {
2517  $flags[] = 'deprecated';
2518  }
2519  if ( $this->isInternal() ) {
2520  $flags[] = 'internal';
2521  }
2522  if ( $this->isReadMode() ) {
2523  $flags[] = 'readrights';
2524  }
2525  if ( $this->isWriteMode() ) {
2526  $flags[] = 'writerights';
2527  }
2528  if ( $this->mustBePosted() ) {
2529  $flags[] = 'mustbeposted';
2530  }
2531 
2532  return $flags;
2533  }
2534 
2546  protected function getModuleSourceInfo() {
2547  global $IP;
2548 
2549  if ( $this->mModuleSource !== false ) {
2550  return $this->mModuleSource;
2551  }
2552 
2553  // First, try to find where the module comes from...
2554  $rClass = new ReflectionClass( $this );
2555  $path = $rClass->getFileName();
2556  if ( !$path ) {
2557  // No path known?
2558  $this->mModuleSource = null;
2559  return null;
2560  }
2561  $path = realpath( $path ) ?: $path;
2562 
2563  // Build map of extension directories to extension info
2564  if ( self::$extensionInfo === null ) {
2565  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2566  self::$extensionInfo = [
2567  realpath( __DIR__ ) ?: __DIR__ => [
2568  'path' => $IP,
2569  'name' => 'MediaWiki',
2570  'license-name' => 'GPL-2.0-or-later',
2571  ],
2572  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2573  realpath( $extDir ) ?: $extDir => null,
2574  ];
2575  $keep = [
2576  'path' => null,
2577  'name' => null,
2578  'namemsg' => null,
2579  'license-name' => null,
2580  ];
2581  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2582  foreach ( $group as $ext ) {
2583  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2584  // This shouldn't happen, but does anyway.
2585  continue;
2586  }
2587 
2588  $extpath = $ext['path'];
2589  if ( !is_dir( $extpath ) ) {
2590  $extpath = dirname( $extpath );
2591  }
2592  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2593  array_intersect_key( $ext, $keep );
2594  }
2595  }
2596  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2597  $extpath = $ext['path'];
2598  if ( !is_dir( $extpath ) ) {
2599  $extpath = dirname( $extpath );
2600  }
2601  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2602  array_intersect_key( $ext, $keep );
2603  }
2604  }
2605 
2606  // Now traverse parent directories until we find a match or run out of
2607  // parents.
2608  do {
2609  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2610  // Found it!
2611  $this->mModuleSource = self::$extensionInfo[$path];
2612  return $this->mModuleSource;
2613  }
2614 
2615  $oldpath = $path;
2616  $path = dirname( $path );
2617  } while ( $path !== $oldpath );
2618 
2619  // No idea what extension this might be.
2620  $this->mModuleSource = null;
2621  return null;
2622  }
2623 
2635  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2636  }
2637 
2640  /************************************************************************/
2654  protected function getDescription() {
2655  wfDeprecated( __METHOD__, '1.25' );
2656  return false;
2657  }
2658 
2671  protected function getParamDescription() {
2672  wfDeprecated( __METHOD__, '1.25' );
2673  return [];
2674  }
2675 
2692  protected function getExamples() {
2693  wfDeprecated( __METHOD__, '1.25' );
2694  return false;
2695  }
2696 
2705  protected function getDescriptionMessage() {
2706  wfDeprecated( __METHOD__, '1.30' );
2707  return [ [
2708  "apihelp-{$this->getModulePath()}-description",
2709  "apihelp-{$this->getModulePath()}-summary",
2710  ] ];
2711  }
2712 
2720  public static function truncateArray( &$arr, $limit ) {
2721  wfDeprecated( __METHOD__, '1.32' );
2722  $modified = false;
2723  while ( count( $arr ) > $limit ) {
2724  array_pop( $arr );
2725  $modified = true;
2726  }
2727 
2728  return $modified;
2729  }
2730 
2732 }
2733 
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:138
setContext(IContextSource $context)
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set...
Definition: ApiBase.php:1002
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1437
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1054
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:2377
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:876
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:647
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:405
getErrorsByType( $type)
Returns a list of status messages of the given type.
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
getResult()
Get the result object.
Definition: ApiBase.php:633
Message subclass that prepends wikitext for API help.
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
static $blockMsgMap
$var array Map of web UI block messages to corresponding API messages and codes
Definition: ApiBase.php:275
getDescriptionMessage()
Return the description message.
Definition: ApiBase.php:2705
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2546
$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:347
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses & $ret
Definition: hooks.txt:1982
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php: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:2062
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1924
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:1848
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1558
getMain()
Get the main module.
Definition: ApiBase.php:529
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:2159
getType()
Get the type of target for this particular block.
Definition: Block.php:1676
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)
Exception used to abort API execution with an error.
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:661
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
static ApiMain $mMainModule
Definition: ApiBase.php:279
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:561
This manages continuation state.
This code would result in ircNotify being run twice when an article is and once for brion Hooks can return three possible true was required This is the default since MediaWiki *some string
Definition: hooks.txt:175
$value
getParent()
Get the parent of this module.
Definition: ApiBase.php:547
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1991
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:744
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:2003
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:286
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1611
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:1683
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:437
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:371
string $mModuleName
Definition: ApiBase.php:285
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:469
IContextSource $context
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2228
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2300
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:859
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:1777
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:48
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1246
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:132
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1263
parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues, $allSpecifier=null, $limit1=null, $limit2=null)
Return an array of values that were given in a &#39;a|b|c&#39; notation, after it optionally validates them a...
Definition: ApiBase.php:1477
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2274
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:1082
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:2720
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:2513
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:942
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:2206
getModulePath()
Get the path to this module.
Definition: ApiBase.php:577
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:673
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:687
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:1449
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:296
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:80
$params
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:41
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1982
Extension of Message implementing IApiMessage.
Definition: ApiMessage.php:26
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition: ApiBase.php:447
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1123
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:1174
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:711
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:513
$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:595
string $mModulePrefix
Definition: ApiBase.php:285
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:2635
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:780
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition: ApiBase.php:914
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:1802
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:334
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1742
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:176
static getTokenTypeSalts()
Get the salts for known token types.
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition: ApiBase.php:1017
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:722
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition: ApiBase.php:2015
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:428
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:498
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:1957
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:2189
filterIDs( $fields, array $ids)
Filter out-of-range values from a list of positive integer IDs.
Definition: ApiBase.php:1862
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:521
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2090
const RE_IP_BYTE
Definition: IP.php:29
getDescription()
Returns the description string for this module.
Definition: ApiBase.php:2654
$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:2316
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1910
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
if(!is_readable( $file)) $ext
Definition: router.php:48
wfTransactionalTimeLimit()
Set PHP&#39;s time limit to the larger of php.ini or $wgTransactionalTimeLimit.
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition: ApiBase.php:2218
getExamples()
Returns usage examples for this module.
Definition: ApiBase.php:2692
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:2031
$mParamCache
Definition: ApiBase.php:287
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:482
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:1648
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1727
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:106
array null bool $mModuleSource
Definition: ApiBase.php:289
const DB_REPLICA
Definition: defines.php:25
getParamDescription()
Returns an array of parameter descriptions.
Definition: ApiBase.php:2671
static canAddTagsAccompanyingChange(array $tags, User $user=null)
Is it OK to allow the user to apply all the specified tags at the same time as they edit/make the cha...
Definition: ChangeTags.php:481
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:362
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:972
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:587
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:397
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:2287
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:2633
getUserPermissionsErrors( $action, $user, $rigor=PermissionManager::RIGOR_SECURE, $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2227
checkTitleUserPermissions(Title $title, $actions, $options=[])
Helper function for permission-denied errors.
Definition: ApiBase.php:2128
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2345
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2106
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'], array $filter=[])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1970
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:1473
static listParam(array $list, $type='text')
Definition: Message.php:1126
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
addBlockInfoToStatus(StatusValue $status, User $user=null)
Add block info to block messages in a Status.
Definition: ApiBase.php:1830
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:538
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:420
replaceMessage( $source, $dest)
If the specified source message exists, replace it with the specified destination message...
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:319
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:387