MediaWiki  master
ApiBase.php
Go to the documentation of this file.
1 <?php
24 
37 abstract class ApiBase extends ContextSource {
38 
48  const PARAM_DFLT = 0;
49 
51  const PARAM_ISMULTI = 1;
52 
87  const PARAM_TYPE = 2;
88 
90  const PARAM_MAX = 3;
91 
96  const PARAM_MAX2 = 4;
97 
99  const PARAM_MIN = 5;
100 
103 
105  const PARAM_DEPRECATED = 7;
106 
111  const PARAM_REQUIRED = 8;
112 
118 
124  const PARAM_HELP_MSG = 10;
125 
132 
142 
148  const PARAM_VALUE_LINKS = 13;
149 
158 
166 
173 
180  const PARAM_ALL = 17;
181 
187 
193  const PARAM_SENSITIVE = 19;
194 
203 
209 
216 
221  const PARAM_MAX_BYTES = 23;
222 
227  const PARAM_MAX_CHARS = 24;
228 
246 
249  const ALL_DEFAULT_STRING = '*';
250 
252  const LIMIT_BIG1 = 500;
254  const LIMIT_BIG2 = 5000;
256  const LIMIT_SML1 = 50;
258  const LIMIT_SML2 = 500;
259 
266 
268  private static $extensionInfo = null;
269 
271  private $mMainModule;
274  private $mSlaveDB = null;
275  private $mParamCache = [];
277  private $mModuleSource = false;
278 
284  public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
285  $this->mMainModule = $mainModule;
286  $this->mModuleName = $moduleName;
287  $this->mModulePrefix = $modulePrefix;
288 
289  if ( !$this->isMain() ) {
290  $this->setContext( $mainModule->getContext() );
291  }
292  }
293 
294  /************************************************************************/
315  abstract public function execute();
316 
322  public function getModuleManager() {
323  return null;
324  }
325 
335  public function getCustomPrinter() {
336  return null;
337  }
338 
350  protected function getExamplesMessages() {
351  // Fall back to old non-localised method
352  $ret = [];
353 
354  $examples = $this->getExamples();
355  if ( $examples ) {
356  if ( !is_array( $examples ) ) {
357  $examples = [ $examples ];
358  } elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
359  array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
360  !preg_match( '/^\s*api\.php\?/', $examples[0] )
361  ) {
362  // Fix up the ugly "even numbered elements are description, odd
363  // numbered elemts are the link" format (see doc for self::getExamples)
364  $tmp = [];
365  $examplesCount = count( $examples );
366  for ( $i = 0; $i < $examplesCount; $i += 2 ) {
367  $tmp[$examples[$i + 1]] = $examples[$i];
368  }
369  $examples = $tmp;
370  }
371 
372  foreach ( $examples as $k => $v ) {
373  if ( is_numeric( $k ) ) {
374  $qs = $v;
375  $msg = '';
376  } else {
377  $qs = $k;
378  $msg = self::escapeWikiText( $v );
379  if ( is_array( $msg ) ) {
380  $msg = implode( ' ', $msg );
381  }
382  }
383 
384  $qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
385  $ret[$qs] = $this->msg( 'api-help-fallback-example', [ $msg ] );
386  }
387  }
388 
389  return $ret;
390  }
391 
397  public function getHelpUrls() {
398  return [];
399  }
400 
413  protected function getAllowedParams( /* $flags = 0 */ ) {
414  // int $flags is not declared because it causes "Strict standards"
415  // warning. Most derived classes do not implement it.
416  return [];
417  }
418 
423  public function shouldCheckMaxlag() {
424  return true;
425  }
426 
431  public function isReadMode() {
432  return true;
433  }
434 
446  public function isWriteMode() {
447  return false;
448  }
449 
454  public function mustBePosted() {
455  return $this->needsToken() !== false;
456  }
457 
463  public function isDeprecated() {
464  return false;
465  }
466 
473  public function isInternal() {
474  return false;
475  }
476 
495  public function needsToken() {
496  return false;
497  }
498 
508  protected function getWebUITokenSalt( array $params ) {
509  return null;
510  }
511 
524  public function getConditionalRequestData( $condition ) {
525  return null;
526  }
527 
530  /************************************************************************/
539  public function getModuleName() {
540  return $this->mModuleName;
541  }
542 
547  public function getModulePrefix() {
548  return $this->mModulePrefix;
549  }
550 
555  public function getMain() {
556  return $this->mMainModule;
557  }
558 
564  public function isMain() {
565  return $this === $this->mMainModule;
566  }
567 
573  public function getParent() {
574  return $this->isMain() ? null : $this->getMain();
575  }
576 
587  public function lacksSameOriginSecurity() {
588  // Main module has this method overridden
589  // Safety - avoid infinite loop:
590  if ( $this->isMain() ) {
591  self::dieDebug( __METHOD__, 'base method was called on main module.' );
592  }
593 
594  return $this->getMain()->lacksSameOriginSecurity();
595  }
596 
603  public function getModulePath() {
604  if ( $this->isMain() ) {
605  return 'main';
606  } elseif ( $this->getParent()->isMain() ) {
607  return $this->getModuleName();
608  } else {
609  return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
610  }
611  }
612 
621  public function getModuleFromPath( $path ) {
622  $module = $this->getMain();
623  if ( $path === 'main' ) {
624  return $module;
625  }
626 
627  $parts = explode( '+', $path );
628  if ( count( $parts ) === 1 ) {
629  // In case the '+' was typed into URL, it resolves as a space
630  $parts = explode( ' ', $path );
631  }
632 
633  $count = count( $parts );
634  for ( $i = 0; $i < $count; $i++ ) {
635  $parent = $module;
636  $manager = $parent->getModuleManager();
637  if ( $manager === null ) {
638  $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
639  $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
640  }
641  $module = $manager->getModule( $parts[$i] );
642 
643  if ( $module === null ) {
644  $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
645  $this->dieWithError(
646  [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
647  'badmodule'
648  );
649  }
650  }
651 
652  return $module;
653  }
654 
659  public function getResult() {
660  // Main module has getResult() method overridden
661  // Safety - avoid infinite loop:
662  if ( $this->isMain() ) {
663  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
664  }
665 
666  return $this->getMain()->getResult();
667  }
668 
673  public function getErrorFormatter() {
674  // Main module has getErrorFormatter() 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()->getErrorFormatter();
681  }
682 
687  protected function getDB() {
688  if ( !isset( $this->mSlaveDB ) ) {
689  $this->mSlaveDB = wfGetDB( DB_REPLICA, 'api' );
690  }
691 
692  return $this->mSlaveDB;
693  }
694 
699  public function getContinuationManager() {
700  // Main module has getContinuationManager() method overridden
701  // Safety - avoid infinite loop:
702  if ( $this->isMain() ) {
703  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
704  }
705 
706  return $this->getMain()->getContinuationManager();
707  }
708 
713  public function setContinuationManager( ApiContinuationManager $manager = null ) {
714  // Main module has setContinuationManager() method overridden
715  // Safety - avoid infinite loop:
716  if ( $this->isMain() ) {
717  self::dieDebug( __METHOD__, 'base method was called on main module. ' );
718  }
719 
720  $this->getMain()->setContinuationManager( $manager );
721  }
722 
725  /************************************************************************/
737  public function dynamicParameterDocumentation() {
738  return null;
739  }
740 
748  public function encodeParamName( $paramName ) {
749  if ( is_array( $paramName ) ) {
750  return array_map( function ( $name ) {
751  return $this->mModulePrefix . $name;
752  }, $paramName );
753  } else {
754  return $this->mModulePrefix . $paramName;
755  }
756  }
757 
770  public function extractRequestParams( $options = [] ) {
771  if ( is_bool( $options ) ) {
772  $options = [ 'parseLimit' => $options ];
773  }
774  $options += [
775  'parseLimit' => true,
776  'safeMode' => false,
777  ];
778 
779  $parseLimit = (bool)$options['parseLimit'];
780 
781  // Cache parameters, for performance and to avoid T26564.
782  if ( !isset( $this->mParamCache[$parseLimit] ) ) {
783  $params = $this->getFinalParams() ?: [];
784  $results = [];
785  $warned = [];
786 
787  // Process all non-templates and save templates for secondary
788  // processing.
789  $toProcess = [];
790  foreach ( $params as $paramName => $paramSettings ) {
791  if ( isset( $paramSettings[self::PARAM_TEMPLATE_VARS] ) ) {
792  $toProcess[] = [ $paramName, $paramSettings[self::PARAM_TEMPLATE_VARS], $paramSettings ];
793  } else {
794  try {
795  $results[$paramName] = $this->getParameterFromSettings(
796  $paramName, $paramSettings, $parseLimit
797  );
798  } catch ( ApiUsageException $ex ) {
799  $results[$paramName] = $ex;
800  }
801  }
802  }
803 
804  // Now process all the templates by successively replacing the
805  // placeholders with all client-supplied values.
806  // This bit duplicates JavaScript logic in
807  // ApiSandbox.PageLayout.prototype.updateTemplatedParams().
808  // If you update this, see if that needs updating too.
809  while ( $toProcess ) {
810  list( $name, $targets, $settings ) = array_shift( $toProcess );
811 
812  foreach ( $targets as $placeholder => $target ) {
813  if ( !array_key_exists( $target, $results ) ) {
814  // The target wasn't processed yet, try the next one.
815  // If all hit this case, the parameter has no expansions.
816  continue;
817  }
818  if ( !is_array( $results[$target] ) || !$results[$target] ) {
819  // The target was processed but has no (valid) values.
820  // That means it has no expansions.
821  break;
822  }
823 
824  // Expand this target in the name and all other targets,
825  // then requeue if there are more targets left or put in
826  // $results if all are done.
827  unset( $targets[$placeholder] );
828  $placeholder = '{' . $placeholder . '}';
829  foreach ( $results[$target] as $value ) {
830  if ( !preg_match( '/^[^{}]*$/', $value ) ) {
831  // Skip values that make invalid parameter names.
832  $encTargetName = $this->encodeParamName( $target );
833  if ( !isset( $warned[$encTargetName][$value] ) ) {
834  $warned[$encTargetName][$value] = true;
835  $this->addWarning( [
836  'apiwarn-ignoring-invalid-templated-value',
837  wfEscapeWikiText( $encTargetName ),
838  wfEscapeWikiText( $value ),
839  ] );
840  }
841  continue;
842  }
843 
844  $newName = str_replace( $placeholder, $value, $name );
845  if ( !$targets ) {
846  try {
847  $results[$newName] = $this->getParameterFromSettings( $newName, $settings, $parseLimit );
848  } catch ( ApiUsageException $ex ) {
849  $results[$newName] = $ex;
850  }
851  } else {
852  $newTargets = [];
853  foreach ( $targets as $k => $v ) {
854  $newTargets[$k] = str_replace( $placeholder, $value, $v );
855  }
856  $toProcess[] = [ $newName, $newTargets, $settings ];
857  }
858  }
859  break;
860  }
861  }
862 
863  $this->mParamCache[$parseLimit] = $results;
864  }
865 
866  $ret = $this->mParamCache[$parseLimit];
867  if ( !$options['safeMode'] ) {
868  foreach ( $ret as $v ) {
869  if ( $v instanceof ApiUsageException ) {
870  throw $v;
871  }
872  }
873  }
874 
875  return $this->mParamCache[$parseLimit];
876  }
877 
884  protected function getParameter( $paramName, $parseLimit = true ) {
885  $ret = $this->extractRequestParams( [
886  'parseLimit' => $parseLimit,
887  'safeMode' => true,
888  ] )[$paramName];
889  if ( $ret instanceof ApiUsageException ) {
890  throw $ret;
891  }
892  return $ret;
893  }
894 
901  public function requireOnlyOneParameter( $params, $required /*...*/ ) {
902  $required = func_get_args();
903  array_shift( $required );
904 
905  $intersection = array_intersect( array_keys( array_filter( $params,
906  [ $this, 'parameterNotEmpty' ] ) ), $required );
907 
908  if ( count( $intersection ) > 1 ) {
909  $this->dieWithError( [
910  'apierror-invalidparammix',
911  Message::listParam( array_map(
912  function ( $p ) {
913  return '<var>' . $this->encodeParamName( $p ) . '</var>';
914  },
915  array_values( $intersection )
916  ) ),
917  count( $intersection ),
918  ] );
919  } elseif ( count( $intersection ) == 0 ) {
920  $this->dieWithError( [
921  'apierror-missingparam-one-of',
922  Message::listParam( array_map(
923  function ( $p ) {
924  return '<var>' . $this->encodeParamName( $p ) . '</var>';
925  },
926  array_values( $required )
927  ) ),
928  count( $required ),
929  ], 'missingparam' );
930  }
931  }
932 
939  public function requireMaxOneParameter( $params, $required /*...*/ ) {
940  $required = func_get_args();
941  array_shift( $required );
942 
943  $intersection = array_intersect( array_keys( array_filter( $params,
944  [ $this, 'parameterNotEmpty' ] ) ), $required );
945 
946  if ( count( $intersection ) > 1 ) {
947  $this->dieWithError( [
948  'apierror-invalidparammix',
949  Message::listParam( array_map(
950  function ( $p ) {
951  return '<var>' . $this->encodeParamName( $p ) . '</var>';
952  },
953  array_values( $intersection )
954  ) ),
955  count( $intersection ),
956  ] );
957  }
958  }
959 
967  public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
968  $required = func_get_args();
969  array_shift( $required );
970 
971  $intersection = array_intersect(
972  array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
973  $required
974  );
975 
976  if ( count( $intersection ) == 0 ) {
977  $this->dieWithError( [
978  'apierror-missingparam-at-least-one-of',
979  Message::listParam( array_map(
980  function ( $p ) {
981  return '<var>' . $this->encodeParamName( $p ) . '</var>';
982  },
983  array_values( $required )
984  ) ),
985  count( $required ),
986  ], 'missingparam' );
987  }
988  }
989 
997  public function requirePostedParameters( $params, $prefix = 'prefix' ) {
998  // Skip if $wgDebugAPI is set or we're in internal mode
999  if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
1000  return;
1001  }
1002 
1003  $queryValues = $this->getRequest()->getQueryValues();
1004  $badParams = [];
1005  foreach ( $params as $param ) {
1006  if ( $prefix !== 'noprefix' ) {
1007  $param = $this->encodeParamName( $param );
1008  }
1009  if ( array_key_exists( $param, $queryValues ) ) {
1010  $badParams[] = $param;
1011  }
1012  }
1013 
1014  if ( $badParams ) {
1015  $this->dieWithError(
1016  [ 'apierror-mustpostparams', implode( ', ', $badParams ), count( $badParams ) ]
1017  );
1018  }
1019  }
1020 
1027  private function parameterNotEmpty( $x ) {
1028  return !is_null( $x ) && $x !== false;
1029  }
1030 
1042  public function getTitleOrPageId( $params, $load = false ) {
1043  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1044 
1045  $pageObj = null;
1046  if ( isset( $params['title'] ) ) {
1047  $titleObj = Title::newFromText( $params['title'] );
1048  if ( !$titleObj || $titleObj->isExternal() ) {
1049  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1050  }
1051  if ( !$titleObj->canExist() ) {
1052  $this->dieWithError( 'apierror-pagecannotexist' );
1053  }
1054  $pageObj = WikiPage::factory( $titleObj );
1055  if ( $load !== false ) {
1056  $pageObj->loadPageData( $load );
1057  }
1058  } elseif ( isset( $params['pageid'] ) ) {
1059  if ( $load === false ) {
1060  $load = 'fromdb';
1061  }
1062  $pageObj = WikiPage::newFromID( $params['pageid'], $load );
1063  if ( !$pageObj ) {
1064  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1065  }
1066  }
1067 
1068  return $pageObj;
1069  }
1070 
1079  public function getTitleFromTitleOrPageId( $params ) {
1080  $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
1081 
1082  $titleObj = null;
1083  if ( isset( $params['title'] ) ) {
1084  $titleObj = Title::newFromText( $params['title'] );
1085  if ( !$titleObj || $titleObj->isExternal() ) {
1086  $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
1087  }
1088  return $titleObj;
1089  } elseif ( isset( $params['pageid'] ) ) {
1090  $titleObj = Title::newFromID( $params['pageid'] );
1091  if ( !$titleObj ) {
1092  $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
1093  }
1094  }
1095 
1096  return $titleObj;
1097  }
1098 
1107  protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
1108  $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
1109 
1110  switch ( $watchlist ) {
1111  case 'watch':
1112  return true;
1113 
1114  case 'unwatch':
1115  return false;
1116 
1117  case 'preferences':
1118  # If the user is already watching, don't bother checking
1119  if ( $userWatching ) {
1120  return true;
1121  }
1122  # If no user option was passed, use watchdefault and watchcreations
1123  if ( is_null( $userOption ) ) {
1124  return $this->getUser()->getBoolOption( 'watchdefault' ) ||
1125  $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
1126  }
1127 
1128  # Watch the article based on the user preference
1129  return $this->getUser()->getBoolOption( $userOption );
1130 
1131  case 'nochange':
1132  return $userWatching;
1133 
1134  default:
1135  return $userWatching;
1136  }
1137  }
1138 
1148  protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1149  // Some classes may decide to change parameter names
1150  $encParamName = $this->encodeParamName( $paramName );
1151 
1152  // Shorthand
1153  if ( !is_array( $paramSettings ) ) {
1154  $paramSettings = [
1155  self::PARAM_DFLT => $paramSettings,
1156  ];
1157  }
1158 
1159  $default = $paramSettings[self::PARAM_DFLT] ?? null;
1160  $multi = $paramSettings[self::PARAM_ISMULTI] ?? false;
1161  $multiLimit1 = $paramSettings[self::PARAM_ISMULTI_LIMIT1] ?? null;
1162  $multiLimit2 = $paramSettings[self::PARAM_ISMULTI_LIMIT2] ?? null;
1163  $type = $paramSettings[self::PARAM_TYPE] ?? null;
1164  $dupes = $paramSettings[self::PARAM_ALLOW_DUPLICATES] ?? false;
1165  $deprecated = $paramSettings[self::PARAM_DEPRECATED] ?? false;
1166  $deprecatedValues = $paramSettings[self::PARAM_DEPRECATED_VALUES] ?? [];
1167  $required = $paramSettings[self::PARAM_REQUIRED] ?? false;
1168  $allowAll = $paramSettings[self::PARAM_ALL] ?? false;
1169 
1170  // When type is not given, and no choices, the type is the same as $default
1171  if ( !isset( $type ) ) {
1172  if ( isset( $default ) ) {
1173  $type = gettype( $default );
1174  } else {
1175  $type = 'NULL'; // allow everything
1176  }
1177  }
1178 
1179  if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1180  $this->getMain()->markParamsSensitive( $encParamName );
1181  }
1182 
1183  if ( $type == 'boolean' ) {
1184  if ( isset( $default ) && $default !== false ) {
1185  // Having a default value of anything other than 'false' is not allowed
1186  self::dieDebug(
1187  __METHOD__,
1188  "Boolean param $encParamName's default is set to '$default'. " .
1189  'Boolean parameters must default to false.'
1190  );
1191  }
1192 
1193  $value = $this->getMain()->getCheck( $encParamName );
1194  } elseif ( $type == 'upload' ) {
1195  if ( isset( $default ) ) {
1196  // Having a default value is not allowed
1197  self::dieDebug(
1198  __METHOD__,
1199  "File upload param $encParamName's default is set to " .
1200  "'$default'. File upload parameters may not have a default." );
1201  }
1202  if ( $multi ) {
1203  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1204  }
1205  $value = $this->getMain()->getUpload( $encParamName );
1206  if ( !$value->exists() ) {
1207  // This will get the value without trying to normalize it
1208  // (because trying to normalize a large binary file
1209  // accidentally uploaded as a field fails spectacularly)
1210  $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1211  if ( $value !== null ) {
1212  $this->dieWithError(
1213  [ 'apierror-badupload', $encParamName ],
1214  "badupload_{$encParamName}"
1215  );
1216  }
1217  }
1218  } else {
1219  $value = $this->getMain()->getVal( $encParamName, $default );
1220 
1221  if ( isset( $value ) && $type == 'namespace' ) {
1223  if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1224  is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1225  ) {
1226  $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1227  }
1228  // Namespace parameters allow ALL_DEFAULT_STRING to be used to
1229  // specify all namespaces irrespective of PARAM_ALL.
1230  $allowAll = true;
1231  }
1232  if ( isset( $value ) && $type == 'submodule' ) {
1233  if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1234  $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1235  } else {
1236  $type = $this->getModuleManager()->getNames( $paramName );
1237  }
1238  }
1239 
1240  $request = $this->getMain()->getRequest();
1241  $rawValue = $request->getRawVal( $encParamName );
1242  if ( $rawValue === null ) {
1243  $rawValue = $default;
1244  }
1245 
1246  // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1247  if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1248  if ( $multi ) {
1249  // This loses the potential checkTitleEncoding() transformation done by
1250  // WebRequest for $_GET. Let's call that a feature.
1251  $value = implode( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1252  } else {
1253  $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1254  }
1255  }
1256 
1257  // Check for NFC normalization, and warn
1258  if ( $rawValue !== $value ) {
1259  $this->handleParamNormalization( $paramName, $value, $rawValue );
1260  }
1261  }
1262 
1263  $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1264  if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1265  self::dieDebug(
1266  __METHOD__,
1267  "For param $encParamName, PARAM_ALL collides with a possible value" );
1268  }
1269  if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1270  $value = $this->parseMultiValue(
1271  $encParamName,
1272  $value,
1273  $multi,
1274  is_array( $type ) ? $type : null,
1275  $allowAll ? $allSpecifier : null,
1276  $multiLimit1,
1277  $multiLimit2
1278  );
1279  }
1280 
1281  if ( isset( $value ) ) {
1282  // More validation only when choices were not given
1283  // choices were validated in parseMultiValue()
1284  if ( !is_array( $type ) ) {
1285  switch ( $type ) {
1286  case 'NULL': // nothing to do
1287  break;
1288  case 'string':
1289  case 'text':
1290  case 'password':
1291  if ( $required && $value === '' ) {
1292  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1293  }
1294  break;
1295  case 'integer': // Force everything using intval() and optionally validate limits
1296  $min = $paramSettings[self::PARAM_MIN] ?? null;
1297  $max = $paramSettings[self::PARAM_MAX] ?? null;
1298  $enforceLimits = $paramSettings[self::PARAM_RANGE_ENFORCE] ?? false;
1299 
1300  if ( is_array( $value ) ) {
1301  $value = array_map( 'intval', $value );
1302  if ( !is_null( $min ) || !is_null( $max ) ) {
1303  foreach ( $value as &$v ) {
1304  $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1305  }
1306  }
1307  } else {
1308  $value = intval( $value );
1309  if ( !is_null( $min ) || !is_null( $max ) ) {
1310  $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1311  }
1312  }
1313  break;
1314  case 'limit':
1315  if ( !$parseLimit ) {
1316  // Don't do any validation whatsoever
1317  break;
1318  }
1319  if ( !isset( $paramSettings[self::PARAM_MAX] )
1320  || !isset( $paramSettings[self::PARAM_MAX2] )
1321  ) {
1322  self::dieDebug(
1323  __METHOD__,
1324  "MAX1 or MAX2 are not defined for the limit $encParamName"
1325  );
1326  }
1327  if ( $multi ) {
1328  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1329  }
1330  $min = $paramSettings[self::PARAM_MIN] ?? 0;
1331  if ( $value == 'max' ) {
1332  $value = $this->getMain()->canApiHighLimits()
1333  ? $paramSettings[self::PARAM_MAX2]
1334  : $paramSettings[self::PARAM_MAX];
1335  $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1336  } else {
1337  $value = intval( $value );
1338  $this->validateLimit(
1339  $paramName,
1340  $value,
1341  $min,
1342  $paramSettings[self::PARAM_MAX],
1343  $paramSettings[self::PARAM_MAX2]
1344  );
1345  }
1346  break;
1347  case 'boolean':
1348  if ( $multi ) {
1349  self::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1350  }
1351  break;
1352  case 'timestamp':
1353  if ( is_array( $value ) ) {
1354  foreach ( $value as $key => $val ) {
1355  $value[$key] = $this->validateTimestamp( $val, $encParamName );
1356  }
1357  } else {
1358  $value = $this->validateTimestamp( $value, $encParamName );
1359  }
1360  break;
1361  case 'user':
1362  if ( is_array( $value ) ) {
1363  foreach ( $value as $key => $val ) {
1364  $value[$key] = $this->validateUser( $val, $encParamName );
1365  }
1366  } else {
1367  $value = $this->validateUser( $value, $encParamName );
1368  }
1369  break;
1370  case 'upload': // nothing to do
1371  break;
1372  case 'tags':
1373  // If change tagging was requested, check that the tags are valid.
1374  if ( !is_array( $value ) && !$multi ) {
1375  $value = [ $value ];
1376  }
1378  if ( !$tagsStatus->isGood() ) {
1379  $this->dieStatus( $tagsStatus );
1380  }
1381  break;
1382  default:
1383  self::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1384  }
1385  }
1386 
1387  // Throw out duplicates if requested
1388  if ( !$dupes && is_array( $value ) ) {
1389  $value = array_unique( $value );
1390  }
1391 
1392  if ( in_array( $type, [ 'NULL', 'string', 'text', 'password' ], true ) ) {
1393  foreach ( (array)$value as $val ) {
1394  if ( isset( $paramSettings[self::PARAM_MAX_BYTES] )
1395  && strlen( $val ) > $paramSettings[self::PARAM_MAX_BYTES]
1396  ) {
1397  $this->dieWithError( [ 'apierror-maxbytes', $encParamName,
1398  $paramSettings[self::PARAM_MAX_BYTES] ] );
1399  }
1400  if ( isset( $paramSettings[self::PARAM_MAX_CHARS] )
1401  && mb_strlen( $val, 'UTF-8' ) > $paramSettings[self::PARAM_MAX_CHARS]
1402  ) {
1403  $this->dieWithError( [ 'apierror-maxchars', $encParamName,
1404  $paramSettings[self::PARAM_MAX_CHARS] ] );
1405  }
1406  }
1407  }
1408 
1409  // Set a warning if a deprecated parameter has been passed
1410  if ( $deprecated && $value !== false ) {
1411  $feature = $encParamName;
1412  $m = $this;
1413  while ( !$m->isMain() ) {
1414  $p = $m->getParent();
1415  $name = $m->getModuleName();
1416  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1417  $feature = "{$param}={$name}&{$feature}";
1418  $m = $p;
1419  }
1420  $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1421  }
1422 
1423  // Set a warning if a deprecated parameter value has been passed
1424  $usedDeprecatedValues = $deprecatedValues && $value !== false
1425  ? array_intersect( array_keys( $deprecatedValues ), (array)$value )
1426  : [];
1427  if ( $usedDeprecatedValues ) {
1428  $feature = "$encParamName=";
1429  $m = $this;
1430  while ( !$m->isMain() ) {
1431  $p = $m->getParent();
1432  $name = $m->getModuleName();
1433  $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1434  $feature = "{$param}={$name}&{$feature}";
1435  $m = $p;
1436  }
1437  foreach ( $usedDeprecatedValues as $v ) {
1438  $msg = $deprecatedValues[$v];
1439  if ( $msg === true ) {
1440  $msg = [ 'apiwarn-deprecation-parameter', "$encParamName=$v" ];
1441  }
1442  $this->addDeprecation( $msg, "$feature$v" );
1443  }
1444  }
1445  } elseif ( $required ) {
1446  $this->dieWithError( [ 'apierror-missingparam', $encParamName ] );
1447  }
1448 
1449  return $value;
1450  }
1451 
1459  protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1460  $encParamName = $this->encodeParamName( $paramName );
1461  $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1462  }
1463 
1471  protected function explodeMultiValue( $value, $limit ) {
1472  if ( substr( $value, 0, 1 ) === "\x1f" ) {
1473  $sep = "\x1f";
1474  $value = substr( $value, 1 );
1475  } else {
1476  $sep = '|';
1477  }
1478 
1479  return explode( $sep, $value, $limit );
1480  }
1481 
1499  protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1500  $allSpecifier = null, $limit1 = null, $limit2 = null
1501  ) {
1502  if ( ( $value === '' || $value === "\x1f" ) && $allowMultiple ) {
1503  return [];
1504  }
1505  $limit1 = $limit1 ?: self::LIMIT_SML1;
1506  $limit2 = $limit2 ?: self::LIMIT_SML2;
1507 
1508  // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1509  // because it unstubs $wgUser
1510  $valuesList = $this->explodeMultiValue( $value, $limit2 + 1 );
1511  $sizeLimit = count( $valuesList ) > $limit1 && $this->mMainModule->canApiHighLimits()
1512  ? $limit2
1513  : $limit1;
1514 
1515  if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1516  count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1517  ) {
1518  return $allowedValues;
1519  }
1520 
1521  if ( count( $valuesList ) > $sizeLimit ) {
1522  $this->dieWithError(
1523  [ 'apierror-toomanyvalues', $valueName, $sizeLimit ],
1524  "too-many-$valueName"
1525  );
1526  }
1527 
1528  if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1529  // T35482 - Allow entries with | in them for non-multiple values
1530  if ( in_array( $value, $allowedValues, true ) ) {
1531  return $value;
1532  }
1533 
1534  $values = array_map( function ( $v ) {
1535  return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1536  }, $allowedValues );
1537  $this->dieWithError( [
1538  'apierror-multival-only-one-of',
1539  $valueName,
1540  Message::listParam( $values ),
1541  count( $values ),
1542  ], "multival_$valueName" );
1543  }
1544 
1545  if ( is_array( $allowedValues ) ) {
1546  // Check for unknown values
1547  $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1548  if ( count( $unknown ) ) {
1549  if ( $allowMultiple ) {
1550  $this->addWarning( [
1551  'apiwarn-unrecognizedvalues',
1552  $valueName,
1553  Message::listParam( $unknown, 'comma' ),
1554  count( $unknown ),
1555  ] );
1556  } else {
1557  $this->dieWithError(
1558  [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1559  "unknown_$valueName"
1560  );
1561  }
1562  }
1563  // Now throw them out
1564  $valuesList = array_intersect( $valuesList, $allowedValues );
1565  }
1566 
1567  return $allowMultiple ? $valuesList : $valuesList[0];
1568  }
1569 
1580  protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1581  $enforceLimits = false
1582  ) {
1583  if ( !is_null( $min ) && $value < $min ) {
1584  $msg = ApiMessage::create(
1585  [ 'apierror-integeroutofrange-belowminimum',
1586  $this->encodeParamName( $paramName ), $min, $value ],
1587  'integeroutofrange',
1588  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1589  );
1590  $this->warnOrDie( $msg, $enforceLimits );
1591  $value = $min;
1592  }
1593 
1594  // Minimum is always validated, whereas maximum is checked only if not
1595  // running in internal call mode
1596  if ( $this->getMain()->isInternalMode() ) {
1597  return;
1598  }
1599 
1600  // Optimization: do not check user's bot status unless really needed -- skips db query
1601  // assumes $botMax >= $max
1602  if ( !is_null( $max ) && $value > $max ) {
1603  if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1604  if ( $value > $botMax ) {
1605  $msg = ApiMessage::create(
1606  [ 'apierror-integeroutofrange-abovebotmax',
1607  $this->encodeParamName( $paramName ), $botMax, $value ],
1608  'integeroutofrange',
1609  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1610  );
1611  $this->warnOrDie( $msg, $enforceLimits );
1612  $value = $botMax;
1613  }
1614  } else {
1615  $msg = ApiMessage::create(
1616  [ 'apierror-integeroutofrange-abovemax',
1617  $this->encodeParamName( $paramName ), $max, $value ],
1618  'integeroutofrange',
1619  [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1620  );
1621  $this->warnOrDie( $msg, $enforceLimits );
1622  $value = $max;
1623  }
1624  }
1625  }
1626 
1633  protected function validateTimestamp( $value, $encParamName ) {
1634  // Confusing synonyms for the current time accepted by wfTimestamp()
1635  // (wfTimestamp() also accepts various non-strings and the string of 14
1636  // ASCII NUL bytes, but those can't get here)
1637  if ( !$value ) {
1638  $this->addDeprecation(
1639  [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1640  'unclear-"now"-timestamp'
1641  );
1642  return wfTimestamp( TS_MW );
1643  }
1644 
1645  // Explicit synonym for the current time
1646  if ( $value === 'now' ) {
1647  return wfTimestamp( TS_MW );
1648  }
1649 
1650  $timestamp = wfTimestamp( TS_MW, $value );
1651  if ( $timestamp === false ) {
1652  $this->dieWithError(
1653  [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1654  "badtimestamp_{$encParamName}"
1655  );
1656  }
1657 
1658  return $timestamp;
1659  }
1660 
1670  final public function validateToken( $token, array $params ) {
1671  $tokenType = $this->needsToken();
1673  if ( !isset( $salts[$tokenType] ) ) {
1674  throw new MWException(
1675  "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1676  'without registering it'
1677  );
1678  }
1679 
1680  $tokenObj = ApiQueryTokens::getToken(
1681  $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1682  );
1683  if ( $tokenObj->match( $token ) ) {
1684  return true;
1685  }
1686 
1687  $webUiSalt = $this->getWebUITokenSalt( $params );
1688  if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1689  $token,
1690  $webUiSalt,
1691  $this->getRequest()
1692  ) ) {
1693  return true;
1694  }
1695 
1696  return false;
1697  }
1698 
1705  private function validateUser( $value, $encParamName ) {
1707  return $value;
1708  }
1709 
1710  $name = User::getCanonicalName( $value, 'valid' );
1711  if ( $name !== false ) {
1712  return $name;
1713  }
1714 
1715  if (
1716  // We allow ranges as well, for blocks.
1717  IP::isIPAddress( $value ) ||
1718  // See comment for User::isIP. We don't just call that function
1719  // here because it also returns true for things like
1720  // 300.300.300.300 that are neither valid usernames nor valid IP
1721  // addresses.
1722  preg_match(
1723  '/^' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.' . RE_IP_BYTE . '\.xxx$/',
1724  $value
1725  )
1726  ) {
1727  return IP::sanitizeIP( $value );
1728  }
1729 
1730  $this->dieWithError(
1731  [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1732  "baduser_{$encParamName}"
1733  );
1734  }
1735 
1738  /************************************************************************/
1749  protected function setWatch( $watch, $titleObj, $userOption = null ) {
1750  $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1751  if ( $value === null ) {
1752  return;
1753  }
1754 
1755  WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1756  }
1757 
1764  public function getWatchlistUser( $params ) {
1765  if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1766  $user = User::newFromName( $params['owner'], false );
1767  if ( !( $user && $user->getId() ) ) {
1768  $this->dieWithError(
1769  [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1770  );
1771  }
1772  $token = $user->getOption( 'watchlisttoken' );
1773  if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1774  $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1775  }
1776  } else {
1777  if ( !$this->getUser()->isLoggedIn() ) {
1778  $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1779  }
1780  $this->checkUserRightsAny( 'viewmywatchlist' );
1781  $user = $this->getUser();
1782  }
1783 
1784  return $user;
1785  }
1786 
1794  private static function escapeWikiText( $v ) {
1795  if ( is_array( $v ) ) {
1796  return array_map( 'self::escapeWikiText', $v );
1797  } else {
1798  return strtr( $v, [
1799  '__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
1800  '[[Category:' => '[[:Category:',
1801  '[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
1802  ] );
1803  }
1804  }
1805 
1818  public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1819  if ( is_string( $msg ) ) {
1820  $msg = wfMessage( $msg );
1821  } elseif ( is_array( $msg ) ) {
1822  $msg = wfMessage( ...$msg );
1823  }
1824  if ( !$msg instanceof Message ) {
1825  return null;
1826  }
1827 
1828  $msg->setContext( $context );
1829  if ( $params ) {
1830  $msg->params( $params );
1831  }
1832 
1833  return $msg;
1834  }
1835 
1843  public function errorArrayToStatus( array $errors, User $user = null ) {
1844  if ( $user === null ) {
1845  $user = $this->getUser();
1846  }
1847 
1849  foreach ( $errors as $error ) {
1850  if ( is_array( $error ) && $error[0] === 'blockedtext' && $user->getBlock() ) {
1851  $status->fatal( ApiMessage::create(
1852  'apierror-blocked',
1853  'blocked',
1854  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1855  ) );
1856  } elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
1857  $status->fatal( ApiMessage::create(
1858  'apierror-autoblocked',
1859  'autoblocked',
1860  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1861  ) );
1862  } elseif ( is_array( $error ) && $error[0] === 'systemblockedtext' && $user->getBlock() ) {
1863  $status->fatal( ApiMessage::create(
1864  'apierror-systemblocked',
1865  'blocked',
1866  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1867  ) );
1868  } else {
1869  $status->fatal( ...(array)$error );
1870  }
1871  }
1872  return $status;
1873  }
1874 
1877  /************************************************************************/
1896  public function addWarning( $msg, $code = null, $data = null ) {
1897  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1898  }
1899 
1910  public function addDeprecation( $msg, $feature, $data = [] ) {
1911  $data = (array)$data;
1912  if ( $feature !== null ) {
1913  $data['feature'] = $feature;
1914  $this->logFeatureUsage( $feature );
1915  }
1916  $this->addWarning( $msg, 'deprecation', $data );
1917 
1918  // No real need to deduplicate here, ApiErrorFormatter does that for
1919  // us (assuming the hook is deterministic).
1920  $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1921  Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1922  if ( count( $msgs ) > 1 ) {
1923  $key = '$' . implode( ' $', range( 1, count( $msgs ) ) );
1924  $msg = ( new RawMessage( $key ) )->params( $msgs );
1925  } else {
1926  $msg = reset( $msgs );
1927  }
1928  $this->getMain()->addWarning( $msg, 'deprecation-help' );
1929  }
1930 
1943  public function addError( $msg, $code = null, $data = null ) {
1944  $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1945  }
1946 
1955  public function addMessagesFromStatus( StatusValue $status, $types = [ 'warning', 'error' ] ) {
1956  $this->getErrorFormatter()->addMessagesFromStatus( $this->getModulePath(), $status, $types );
1957  }
1958 
1972  public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
1973  throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
1974  }
1975 
1984  public function dieWithException( $exception, array $options = [] ) {
1985  $this->dieWithError(
1986  $this->getErrorFormatter()->getMessageFromException( $exception, $options )
1987  );
1988  }
1989 
1996  private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
1997  if ( $enforceLimits ) {
1998  $this->dieWithError( $msg );
1999  } else {
2000  $this->addWarning( $msg );
2001  }
2002  }
2003 
2012  public function dieBlocked( Block $block ) {
2013  // Die using the appropriate message depending on block type
2014  if ( $block->getType() == Block::TYPE_AUTO ) {
2015  $this->dieWithError(
2016  'apierror-autoblocked',
2017  'autoblocked',
2018  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2019  );
2020  } else {
2021  $this->dieWithError(
2022  'apierror-blocked',
2023  'blocked',
2024  [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
2025  );
2026  }
2027  }
2028 
2037  public function dieStatus( StatusValue $status ) {
2038  if ( $status->isGood() ) {
2039  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2040  }
2041 
2042  // ApiUsageException needs a fatal status, but this method has
2043  // historically accepted any non-good status. Convert it if necessary.
2044  $status->setOK( false );
2045  if ( !$status->getErrorsByType( 'error' ) ) {
2046  $newStatus = Status::newGood();
2047  foreach ( $status->getErrorsByType( 'warning' ) as $err ) {
2048  $newStatus->fatal( $err['message'], ...$err['params'] );
2049  }
2050  if ( !$newStatus->getErrorsByType( 'error' ) ) {
2051  $newStatus->fatal( 'unknownerror-nocode' );
2052  }
2053  $status = $newStatus;
2054  }
2055 
2056  throw new ApiUsageException( $this, $status );
2057  }
2058 
2064  public function dieReadOnly() {
2065  $this->dieWithError(
2066  'apierror-readonly',
2067  'readonly',
2068  [ 'readonlyreason' => wfReadOnlyReason() ]
2069  );
2070  }
2071 
2080  public function checkUserRightsAny( $rights, $user = null ) {
2081  if ( !$user ) {
2082  $user = $this->getUser();
2083  }
2084  $rights = (array)$rights;
2085  if ( !$user->isAllowedAny( ...$rights ) ) {
2086  $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
2087  }
2088  }
2089 
2098  public function checkTitleUserPermissions( Title $title, $actions, $user = null ) {
2099  if ( !$user ) {
2100  $user = $this->getUser();
2101  }
2102 
2103  $errors = [];
2104  foreach ( (array)$actions as $action ) {
2105  $errors = array_merge( $errors, $title->getUserPermissionsErrors( $action, $user ) );
2106  }
2107  if ( $errors ) {
2108  $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
2109  }
2110  }
2111 
2123  public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
2124  if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2125  $this->dieWithError( $msg, $code, $data, $httpCode );
2126  } else {
2127  $this->addWarning( $msg, $code, $data );
2128  }
2129  }
2130 
2140  protected function dieContinueUsageIf( $condition ) {
2141  if ( $condition ) {
2142  $this->dieWithError( 'apierror-badcontinue' );
2143  }
2144  }
2145 
2152  protected static function dieDebug( $method, $message ) {
2153  throw new MWException( "Internal error in $method: $message" );
2154  }
2155 
2162  public function logFeatureUsage( $feature ) {
2163  $request = $this->getRequest();
2164  $s = '"' . addslashes( $feature ) . '"' .
2165  ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
2166  ' "' . $request->getIP() . '"' .
2167  ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
2168  ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
2169  wfDebugLog( 'api-feature-usage', $s, 'private' );
2170  }
2171 
2174  /************************************************************************/
2188  protected function getSummaryMessage() {
2189  return "apihelp-{$this->getModulePath()}-summary";
2190  }
2191 
2201  protected function getExtendedDescription() {
2202  return [ [
2203  "apihelp-{$this->getModulePath()}-extended-description",
2204  'api-help-no-extended-description',
2205  ] ];
2206  }
2207 
2218  public function getFinalSummary() {
2219  $msg = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2220  $this->getModulePrefix(),
2221  $this->getModuleName(),
2222  $this->getModulePath(),
2223  ] );
2224  if ( !$msg->exists() ) {
2225  wfDeprecated( 'API help "description" messages', '1.30' );
2226  $msg = self::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2227  $this->getModulePrefix(),
2228  $this->getModuleName(),
2229  $this->getModulePath(),
2230  ] );
2231  $msg = self::makeMessage( 'rawmessage', $this->getContext(), [
2232  preg_replace( '/\n.*/s', '', $msg->text() )
2233  ] );
2234  }
2235  return $msg;
2236  }
2237 
2245  public function getFinalDescription() {
2246  $desc = $this->getDescription();
2247 
2248  // Avoid PHP 7.1 warning of passing $this by reference
2249  $apiModule = $this;
2250  Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ] );
2251  $desc = self::escapeWikiText( $desc );
2252  if ( is_array( $desc ) ) {
2253  $desc = implode( "\n", $desc );
2254  } else {
2255  $desc = (string)$desc;
2256  }
2257 
2258  $summary = self::makeMessage( $this->getSummaryMessage(), $this->getContext(), [
2259  $this->getModulePrefix(),
2260  $this->getModuleName(),
2261  $this->getModulePath(),
2262  ] );
2263  $extendedDescription = self::makeMessage(
2264  $this->getExtendedDescription(), $this->getContext(), [
2265  $this->getModulePrefix(),
2266  $this->getModuleName(),
2267  $this->getModulePath(),
2268  ]
2269  );
2270 
2271  if ( $summary->exists() ) {
2272  $msgs = [ $summary, $extendedDescription ];
2273  } else {
2274  wfDeprecated( 'API help "description" messages', '1.30' );
2275  $description = self::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2276  $this->getModulePrefix(),
2277  $this->getModuleName(),
2278  $this->getModulePath(),
2279  ] );
2280  if ( !$description->exists() ) {
2281  $description = $this->msg( 'api-help-fallback-description', $desc );
2282  }
2283  $msgs = [ $description ];
2284  }
2285 
2286  Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2287 
2288  return $msgs;
2289  }
2290 
2299  public function getFinalParams( $flags = 0 ) {
2300  $params = $this->getAllowedParams( $flags );
2301  if ( !$params ) {
2302  $params = [];
2303  }
2304 
2305  if ( $this->needsToken() ) {
2306  $params['token'] = [
2307  self::PARAM_TYPE => 'string',
2308  self::PARAM_REQUIRED => true,
2309  self::PARAM_SENSITIVE => true,
2310  self::PARAM_HELP_MSG => [
2311  'api-help-param-token',
2312  $this->needsToken(),
2313  ],
2314  ] + ( $params['token'] ?? [] );
2315  }
2316 
2317  // Avoid PHP 7.1 warning of passing $this by reference
2318  $apiModule = $this;
2319  Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2320 
2321  return $params;
2322  }
2323 
2331  public function getFinalParamDescription() {
2332  $prefix = $this->getModulePrefix();
2333  $name = $this->getModuleName();
2334  $path = $this->getModulePath();
2335 
2336  $desc = $this->getParamDescription();
2337 
2338  // Avoid PHP 7.1 warning of passing $this by reference
2339  $apiModule = $this;
2340  Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ] );
2341 
2342  if ( !$desc ) {
2343  $desc = [];
2344  }
2345  $desc = self::escapeWikiText( $desc );
2346 
2347  $params = $this->getFinalParams( self::GET_VALUES_FOR_HELP );
2348  $msgs = [];
2349  foreach ( $params as $param => $settings ) {
2350  if ( !is_array( $settings ) ) {
2351  $settings = [];
2352  }
2353 
2354  $d = $desc[$param] ?? '';
2355  if ( is_array( $d ) ) {
2356  // Special handling for prop parameters
2357  $d = array_map( function ( $line ) {
2358  if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
2359  $line = "\n;{$m[1]}:{$m[2]}";
2360  }
2361  return $line;
2362  }, $d );
2363  $d = implode( ' ', $d );
2364  }
2365 
2366  if ( isset( $settings[self::PARAM_HELP_MSG] ) ) {
2367  $msg = $settings[self::PARAM_HELP_MSG];
2368  } else {
2369  $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2370  if ( !$msg->exists() ) {
2371  $msg = $this->msg( 'api-help-fallback-parameter', $d );
2372  }
2373  }
2374  $msg = self::makeMessage( $msg, $this->getContext(),
2375  [ $prefix, $param, $name, $path ] );
2376  if ( !$msg ) {
2377  self::dieDebug( __METHOD__,
2378  'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2379  }
2380  $msgs[$param] = [ $msg ];
2381 
2382  if ( isset( $settings[self::PARAM_TYPE] ) &&
2383  $settings[self::PARAM_TYPE] === 'submodule'
2384  ) {
2385  if ( isset( $settings[self::PARAM_SUBMODULE_MAP] ) ) {
2386  $map = $settings[self::PARAM_SUBMODULE_MAP];
2387  } else {
2388  $prefix = $this->isMain() ? '' : ( $this->getModulePath() . '+' );
2389  $map = [];
2390  foreach ( $this->getModuleManager()->getNames( $param ) as $submoduleName ) {
2391  $map[$submoduleName] = $prefix . $submoduleName;
2392  }
2393  }
2394  ksort( $map );
2395  $submodules = [];
2396  $deprecatedSubmodules = [];
2397  foreach ( $map as $v => $m ) {
2398  $arr = &$submodules;
2399  $isDeprecated = false;
2400  $summary = null;
2401  try {
2402  $submod = $this->getModuleFromPath( $m );
2403  if ( $submod ) {
2404  $summary = $submod->getFinalSummary();
2405  $isDeprecated = $submod->isDeprecated();
2406  if ( $isDeprecated ) {
2407  $arr = &$deprecatedSubmodules;
2408  }
2409  }
2410  } catch ( ApiUsageException $ex ) {
2411  // Ignore
2412  }
2413  if ( $summary ) {
2414  $key = $summary->getKey();
2415  $params = $summary->getParams();
2416  } else {
2417  $key = 'api-help-undocumented-module';
2418  $params = [ $m ];
2419  }
2420  $m = new ApiHelpParamValueMessage( "[[Special:ApiHelp/$m|$v]]", $key, $params, $isDeprecated );
2421  $arr[] = $m->setContext( $this->getContext() );
2422  }
2423  $msgs[$param] = array_merge( $msgs[$param], $submodules, $deprecatedSubmodules );
2424  } elseif ( isset( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2425  if ( !is_array( $settings[self::PARAM_HELP_MSG_PER_VALUE] ) ) {
2426  self::dieDebug( __METHOD__,
2427  'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2428  }
2429  if ( !is_array( $settings[self::PARAM_TYPE] ) ) {
2430  self::dieDebug( __METHOD__,
2431  'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2432  'ApiBase::PARAM_TYPE is an array' );
2433  }
2434 
2435  $valueMsgs = $settings[self::PARAM_HELP_MSG_PER_VALUE];
2436  $deprecatedValues = $settings[self::PARAM_DEPRECATED_VALUES] ?? [];
2437 
2438  foreach ( $settings[self::PARAM_TYPE] as $value ) {
2439  if ( isset( $valueMsgs[$value] ) ) {
2440  $msg = $valueMsgs[$value];
2441  } else {
2442  $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2443  }
2444  $m = self::makeMessage( $msg, $this->getContext(),
2445  [ $prefix, $param, $name, $path, $value ] );
2446  if ( $m ) {
2447  $m = new ApiHelpParamValueMessage(
2448  $value,
2449  [ $m->getKey(), 'api-help-param-no-description' ],
2450  $m->getParams(),
2451  isset( $deprecatedValues[$value] )
2452  );
2453  $msgs[$param][] = $m->setContext( $this->getContext() );
2454  } else {
2455  self::dieDebug( __METHOD__,
2456  "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2457  }
2458  }
2459  }
2460 
2461  if ( isset( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2462  if ( !is_array( $settings[self::PARAM_HELP_MSG_APPEND] ) ) {
2463  self::dieDebug( __METHOD__,
2464  'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2465  }
2466  foreach ( $settings[self::PARAM_HELP_MSG_APPEND] as $m ) {
2467  $m = self::makeMessage( $m, $this->getContext(),
2468  [ $prefix, $param, $name, $path ] );
2469  if ( $m ) {
2470  $msgs[$param][] = $m;
2471  } else {
2472  self::dieDebug( __METHOD__,
2473  'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2474  }
2475  }
2476  }
2477  }
2478 
2479  Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2480 
2481  return $msgs;
2482  }
2483 
2493  protected function getHelpFlags() {
2494  $flags = [];
2495 
2496  if ( $this->isDeprecated() ) {
2497  $flags[] = 'deprecated';
2498  }
2499  if ( $this->isInternal() ) {
2500  $flags[] = 'internal';
2501  }
2502  if ( $this->isReadMode() ) {
2503  $flags[] = 'readrights';
2504  }
2505  if ( $this->isWriteMode() ) {
2506  $flags[] = 'writerights';
2507  }
2508  if ( $this->mustBePosted() ) {
2509  $flags[] = 'mustbeposted';
2510  }
2511 
2512  return $flags;
2513  }
2514 
2526  protected function getModuleSourceInfo() {
2527  global $IP;
2528 
2529  if ( $this->mModuleSource !== false ) {
2530  return $this->mModuleSource;
2531  }
2532 
2533  // First, try to find where the module comes from...
2534  $rClass = new ReflectionClass( $this );
2535  $path = $rClass->getFileName();
2536  if ( !$path ) {
2537  // No path known?
2538  $this->mModuleSource = null;
2539  return null;
2540  }
2541  $path = realpath( $path ) ?: $path;
2542 
2543  // Build map of extension directories to extension info
2544  if ( self::$extensionInfo === null ) {
2545  $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2546  self::$extensionInfo = [
2547  realpath( __DIR__ ) ?: __DIR__ => [
2548  'path' => $IP,
2549  'name' => 'MediaWiki',
2550  'license-name' => 'GPL-2.0-or-later',
2551  ],
2552  realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2553  realpath( $extDir ) ?: $extDir => null,
2554  ];
2555  $keep = [
2556  'path' => null,
2557  'name' => null,
2558  'namemsg' => null,
2559  'license-name' => null,
2560  ];
2561  foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2562  foreach ( $group as $ext ) {
2563  if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2564  // This shouldn't happen, but does anyway.
2565  continue;
2566  }
2567 
2568  $extpath = $ext['path'];
2569  if ( !is_dir( $extpath ) ) {
2570  $extpath = dirname( $extpath );
2571  }
2572  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2573  array_intersect_key( $ext, $keep );
2574  }
2575  }
2576  foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2577  $extpath = $ext['path'];
2578  if ( !is_dir( $extpath ) ) {
2579  $extpath = dirname( $extpath );
2580  }
2581  self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2582  array_intersect_key( $ext, $keep );
2583  }
2584  }
2585 
2586  // Now traverse parent directories until we find a match or run out of
2587  // parents.
2588  do {
2589  if ( array_key_exists( $path, self::$extensionInfo ) ) {
2590  // Found it!
2591  $this->mModuleSource = self::$extensionInfo[$path];
2592  return $this->mModuleSource;
2593  }
2594 
2595  $oldpath = $path;
2596  $path = dirname( $path );
2597  } while ( $path !== $oldpath );
2598 
2599  // No idea what extension this might be.
2600  $this->mModuleSource = null;
2601  return null;
2602  }
2603 
2615  public function modifyHelp( array &$help, array $options, array &$tocData ) {
2616  }
2617 
2620  /************************************************************************/
2634  protected function getDescription() {
2635  return false;
2636  }
2637 
2650  protected function getParamDescription() {
2651  return [];
2652  }
2653 
2670  protected function getExamples() {
2671  return false;
2672  }
2673 
2677  public function profileIn() {
2678  wfDeprecated( __METHOD__, '1.25' );
2679  }
2680 
2684  public function profileOut() {
2685  wfDeprecated( __METHOD__, '1.25' );
2686  }
2687 
2691  public function safeProfileOut() {
2692  wfDeprecated( __METHOD__, '1.25' );
2693  }
2694 
2698  public function profileDBIn() {
2699  wfDeprecated( __METHOD__, '1.25' );
2700  }
2701 
2705  public function profileDBOut() {
2706  wfDeprecated( __METHOD__, '1.25' );
2707  }
2708 
2713  protected function useTransactionalTimeLimit() {
2714  if ( $this->getRequest()->wasPosted() ) {
2716  }
2717  }
2718 
2723  public function setWarning( $warning ) {
2724  wfDeprecated( __METHOD__, '1.29' );
2725  $msg = new ApiRawMessage( $warning, 'warning' );
2726  $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg );
2727  }
2728 
2742  public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
2743  wfDeprecated( __METHOD__, '1.29' );
2744  $this->dieWithError(
2745  new RawMessage( '$1', [ $description ] ),
2746  $errorCode,
2747  $extradata,
2748  $httpRespCode
2749  );
2750  }
2751 
2762  public function getErrorFromStatus( $status, &$extraData = null ) {
2763  wfDeprecated( __METHOD__, '1.29' );
2764  if ( $status->isGood() ) {
2765  throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2766  }
2767 
2768  $errors = $status->getErrorsByType( 'error' );
2769  if ( !$errors ) {
2770  // No errors? Assume the warnings should be treated as errors
2771  $errors = $status->getErrorsByType( 'warning' );
2772  }
2773  if ( !$errors ) {
2774  // Still no errors? Punt
2775  $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
2776  }
2777 
2778  if ( $errors[0]['message'] instanceof MessageSpecifier ) {
2779  $msg = $errors[0]['message'];
2780  } else {
2781  $msg = new Message( $errors[0]['message'], $errors[0]['params'] );
2782  }
2783  if ( !$msg instanceof IApiMessage ) {
2784  $key = $msg->getKey();
2785  $params = $msg->getParams();
2786  array_unshift( $params, self::$messageMap[$key] ?? $key );
2787  $msg = ApiMessage::create( $params );
2788  }
2789 
2790  return [
2791  $msg->getApiCode(),
2792  ApiErrorFormatter::stripMarkup( $msg->inLanguage( 'en' )->useDatabase( false )->text() )
2793  ];
2794  }
2795 
2804  private static $messageMap = [
2805  'unknownerror' => 'apierror-unknownerror',
2806  'unknownerror-nocode' => 'apierror-unknownerror-nocode',
2807  'ns-specialprotected' => 'ns-specialprotected',
2808  'protectedinterface' => 'protectedinterface',
2809  'namespaceprotected' => 'namespaceprotected',
2810  'customcssprotected' => 'customcssprotected',
2811  'customjsprotected' => 'customjsprotected',
2812  'cascadeprotected' => 'cascadeprotected',
2813  'protectedpagetext' => 'protectedpagetext',
2814  'protect-cantedit' => 'protect-cantedit',
2815  'deleteprotected' => 'deleteprotected',
2816  'badaccess-group0' => 'badaccess-group0',
2817  'badaccess-groups' => 'badaccess-groups',
2818  'titleprotected' => 'titleprotected',
2819  'nocreate-loggedin' => 'nocreate-loggedin',
2820  'nocreatetext' => 'nocreatetext',
2821  'movenologintext' => 'movenologintext',
2822  'movenotallowed' => 'movenotallowed',
2823  'confirmedittext' => 'confirmedittext',
2824  'blockedtext' => 'apierror-blocked',
2825  'autoblockedtext' => 'apierror-autoblocked',
2826  'systemblockedtext' => 'apierror-systemblocked',
2827  'actionthrottledtext' => 'apierror-ratelimited',
2828  'alreadyrolled' => 'alreadyrolled',
2829  'cantrollback' => 'cantrollback',
2830  'readonlytext' => 'readonlytext',
2831  'sessionfailure' => 'sessionfailure',
2832  'cannotdelete' => 'cannotdelete',
2833  'notanarticle' => 'apierror-missingtitle',
2834  'selfmove' => 'selfmove',
2835  'immobile_namespace' => 'apierror-immobilenamespace',
2836  'articleexists' => 'articleexists',
2837  'hookaborted' => 'hookaborted',
2838  'cantmove-titleprotected' => 'cantmove-titleprotected',
2839  'imagenocrossnamespace' => 'imagenocrossnamespace',
2840  'imagetypemismatch' => 'imagetypemismatch',
2841  'ip_range_invalid' => 'ip_range_invalid',
2842  'range_block_disabled' => 'range_block_disabled',
2843  'nosuchusershort' => 'nosuchusershort',
2844  'badipaddress' => 'badipaddress',
2845  'ipb_expiry_invalid' => 'ipb_expiry_invalid',
2846  'ipb_already_blocked' => 'ipb_already_blocked',
2847  'ipb_blocked_as_range' => 'ipb_blocked_as_range',
2848  'ipb_cant_unblock' => 'ipb_cant_unblock',
2849  'mailnologin' => 'apierror-cantsend',
2850  'ipbblocked' => 'ipbblocked',
2851  'ipbnounblockself' => 'ipbnounblockself',
2852  'usermaildisabled' => 'usermaildisabled',
2853  'blockedemailuser' => 'apierror-blockedfrommail',
2854  'notarget' => 'apierror-notarget',
2855  'noemail' => 'noemail',
2856  'rcpatroldisabled' => 'rcpatroldisabled',
2857  'markedaspatrollederror-noautopatrol' => 'markedaspatrollederror-noautopatrol',
2858  'delete-toobig' => 'delete-toobig',
2859  'movenotallowedfile' => 'movenotallowedfile',
2860  'userrights-no-interwiki' => 'userrights-no-interwiki',
2861  'userrights-nodatabase' => 'userrights-nodatabase',
2862  'nouserspecified' => 'nouserspecified',
2863  'noname' => 'noname',
2864  'summaryrequired' => 'apierror-summaryrequired',
2865  'import-rootpage-invalid' => 'import-rootpage-invalid',
2866  'import-rootpage-nosubpage' => 'import-rootpage-nosubpage',
2867  'readrequired' => 'apierror-readapidenied',
2868  'writedisabled' => 'apierror-noapiwrite',
2869  'writerequired' => 'apierror-writeapidenied',
2870  'missingparam' => 'apierror-missingparam',
2871  'invalidtitle' => 'apierror-invalidtitle',
2872  'nosuchpageid' => 'apierror-nosuchpageid',
2873  'nosuchrevid' => 'apierror-nosuchrevid',
2874  'nosuchuser' => 'nosuchusershort',
2875  'invaliduser' => 'apierror-invaliduser',
2876  'invalidexpiry' => 'apierror-invalidexpiry',
2877  'pastexpiry' => 'apierror-pastexpiry',
2878  'create-titleexists' => 'apierror-create-titleexists',
2879  'missingtitle-createonly' => 'apierror-missingtitle-createonly',
2880  'cantblock' => 'apierror-cantblock',
2881  'canthide' => 'apierror-canthide',
2882  'cantblock-email' => 'apierror-cantblock-email',
2883  'cantunblock' => 'apierror-permissiondenied-generic',
2884  'cannotundelete' => 'cannotundelete',
2885  'permdenied-undelete' => 'apierror-permissiondenied-generic',
2886  'createonly-exists' => 'apierror-articleexists',
2887  'nocreate-missing' => 'apierror-missingtitle',
2888  'cantchangecontentmodel' => 'apierror-cantchangecontentmodel',
2889  'nosuchrcid' => 'apierror-nosuchrcid',
2890  'nosuchlogid' => 'apierror-nosuchlogid',
2891  'protect-invalidaction' => 'apierror-protect-invalidaction',
2892  'protect-invalidlevel' => 'apierror-protect-invalidlevel',
2893  'toofewexpiries' => 'apierror-toofewexpiries',
2894  'cantimport' => 'apierror-cantimport',
2895  'cantimport-upload' => 'apierror-cantimport-upload',
2896  'importnofile' => 'importnofile',
2897  'importuploaderrorsize' => 'importuploaderrorsize',
2898  'importuploaderrorpartial' => 'importuploaderrorpartial',
2899  'importuploaderrortemp' => 'importuploaderrortemp',
2900  'importcantopen' => 'importcantopen',
2901  'import-noarticle' => 'import-noarticle',
2902  'importbadinterwiki' => 'importbadinterwiki',
2903  'import-unknownerror' => 'apierror-import-unknownerror',
2904  'cantoverwrite-sharedfile' => 'apierror-cantoverwrite-sharedfile',
2905  'sharedfile-exists' => 'apierror-fileexists-sharedrepo-perm',
2906  'mustbeposted' => 'apierror-mustbeposted',
2907  'show' => 'apierror-show',
2908  'specialpage-cantexecute' => 'apierror-specialpage-cantexecute',
2909  'invalidoldimage' => 'apierror-invalidoldimage',
2910  'nodeleteablefile' => 'apierror-nodeleteablefile',
2911  'fileexists-forbidden' => 'fileexists-forbidden',
2912  'fileexists-shared-forbidden' => 'fileexists-shared-forbidden',
2913  'filerevert-badversion' => 'filerevert-badversion',
2914  'noimageredirect-anon' => 'apierror-noimageredirect-anon',
2915  'noimageredirect-logged' => 'apierror-noimageredirect',
2916  'spamdetected' => 'apierror-spamdetected',
2917  'contenttoobig' => 'apierror-contenttoobig',
2918  'noedit-anon' => 'apierror-noedit-anon',
2919  'noedit' => 'apierror-noedit',
2920  'wasdeleted' => 'apierror-pagedeleted',
2921  'blankpage' => 'apierror-emptypage',
2922  'editconflict' => 'editconflict',
2923  'hashcheckfailed' => 'apierror-badmd5',
2924  'missingtext' => 'apierror-notext',
2925  'emptynewsection' => 'apierror-emptynewsection',
2926  'revwrongpage' => 'apierror-revwrongpage',
2927  'undo-failure' => 'undo-failure',
2928  'content-not-allowed-here' => 'content-not-allowed-here',
2929  'edit-hook-aborted' => 'edit-hook-aborted',
2930  'edit-gone-missing' => 'edit-gone-missing',
2931  'edit-conflict' => 'edit-conflict',
2932  'edit-already-exists' => 'edit-already-exists',
2933  'invalid-file-key' => 'apierror-invalid-file-key',
2934  'nouploadmodule' => 'apierror-nouploadmodule',
2935  'uploaddisabled' => 'uploaddisabled',
2936  'copyuploaddisabled' => 'copyuploaddisabled',
2937  'copyuploadbaddomain' => 'apierror-copyuploadbaddomain',
2938  'copyuploadbadurl' => 'apierror-copyuploadbadurl',
2939  'filename-tooshort' => 'filename-tooshort',
2940  'filename-toolong' => 'filename-toolong',
2941  'illegal-filename' => 'illegal-filename',
2942  'filetype-missing' => 'filetype-missing',
2943  'mustbeloggedin' => 'apierror-mustbeloggedin',
2944  ];
2945 
2951  private function parseMsgInternal( $error ) {
2952  $msg = Message::newFromSpecifier( $error );
2953  if ( !$msg instanceof IApiMessage ) {
2954  $key = $msg->getKey();
2955  if ( isset( self::$messageMap[$key] ) ) {
2956  $params = $msg->getParams();
2957  array_unshift( $params, self::$messageMap[$key] );
2958  } else {
2959  $params = [ 'apierror-unknownerror', wfEscapeWikiText( $key ) ];
2960  }
2961  $msg = ApiMessage::create( $params );
2962  }
2963  return $msg;
2964  }
2965 
2972  public function parseMsg( $error ) {
2973  wfDeprecated( __METHOD__, '1.29' );
2974  // Check whether someone passed the whole array, instead of one element as
2975  // documented. This breaks if it's actually an array of fallback keys, but
2976  // that's long-standing misbehavior introduced in r87627 to incorrectly
2977  // fix T30797.
2978  if ( is_array( $error ) ) {
2979  $first = reset( $error );
2980  if ( is_array( $first ) ) {
2981  wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
2982  $error = $first;
2983  }
2984  }
2985 
2986  $msg = $this->parseMsgInternal( $error );
2987  return [
2988  'code' => $msg->getApiCode(),
2990  $msg->inLanguage( 'en' )->useDatabase( false )->text()
2991  ),
2992  'data' => $msg->getApiData()
2993  ];
2994  }
2995 
3002  public function dieUsageMsg( $error ) {
3003  wfDeprecated( __METHOD__, '1.29' );
3004  $this->dieWithError( $this->parseMsgInternal( $error ) );
3005  }
3006 
3015  public function dieUsageMsgOrDebug( $error ) {
3016  wfDeprecated( __METHOD__, '1.29' );
3017  $this->dieWithErrorOrDebug( $this->parseMsgInternal( $error ) );
3018  }
3019 
3028  protected function getDescriptionMessage() {
3029  return [ [
3030  "apihelp-{$this->getModulePath()}-description",
3031  "apihelp-{$this->getModulePath()}-summary",
3032  ] ];
3033  }
3034 
3042  public static function truncateArray( &$arr, $limit ) {
3043  wfDeprecated( __METHOD__, '1.32' );
3044  $modified = false;
3045  while ( count( $arr ) > $limit ) {
3046  array_pop( $arr );
3047  $modified = true;
3048  }
3049 
3050  return $modified;
3051  }
3052 
3054 }
3055 
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:125
setContext(IContextSource $context)
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set...
Definition: ApiBase.php:1027
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition: ApiBase.php:1459
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition: ApiBase.php:1079
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:148
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2331
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below...
Definition: ApiBase.php:87
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false. ...
Definition: ApiBase.php:901
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:673
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition: ApiBase.php:254
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.
profileDBOut()
Definition: ApiBase.php:2705
const PARAM_MAX_BYTES
(integer) Maximum length of a string in bytes (in UTF-8 encoding).
Definition: ApiBase.php:221
isReadMode()
Indicates whether this module requires read rights.
Definition: ApiBase.php:431
getErrorsByType( $type)
Returns a list of status messages of the given type.
getResult()
Get the result object.
Definition: ApiBase.php:659
static $messageMap
Definition: ApiBase.php:2804
Message subclass that prepends wikitext for API help.
dieUsageMsg( $error)
Output the error message related to a certain array.
Definition: ApiBase.php:3002
static stripMarkup( $text)
Turn wikitext into something resembling plaintext.
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:420
getDescriptionMessage()
Return the description message.
Definition: ApiBase.php:3028
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition: ApiBase.php:2526
$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:335
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:2041
static array $extensionInfo
Maps extension paths to info arrays.
Definition: ApiBase.php:268
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:2037
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition: ApiBase.php:1910
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition: ApiBase.php:2713
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition: ApiBase.php:1580
getMain()
Get the main module.
Definition: ApiBase.php:555
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition: ApiBase.php:48
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
getType()
Get the type of target for this particular block.
Definition: Block.php:1450
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition: ApiBase.php:265
const LIMIT_BIG1
Fast query, standard limit.
Definition: ApiBase.php:252
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
safeProfileOut()
Definition: ApiBase.php:2691
checkTitleUserPermissions(Title $title, $actions, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2098
Exception used to abort API execution with an error.
getDB()
Gets a default replica DB connection object.
Definition: ApiBase.php:687
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1300
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:90
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
static escapeWikiText( $v)
A subset of wfEscapeWikiText for BC texts.
Definition: ApiBase.php:1794
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition: ApiBase.php:111
ApiMain $mMainModule
Definition: ApiBase.php:271
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition: ApiBase.php:141
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:587
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:573
getUserPermissionsErrors( $action, $user, $rigor='secure', $ignoreErrors=[])
Can $user perform $action on this page?
Definition: Title.php:2179
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition: ApiBase.php:1972
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user...
Definition: ApiBase.php:770
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:1984
validateTimestamp( $value, $encParamName)
Validate and normalize parameters of type &#39;timestamp&#39;.
Definition: ApiBase.php:1633
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:157
Interface for messages with machine-readable data for use by the API.
Definition: IApiMessage.php:39
validateUser( $value, $encParamName)
Validate and normalize parameters of type &#39;user&#39;.
Definition: ApiBase.php:1705
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true...
Definition: ApiBase.php:180
isDeprecated()
Indicates whether this module is deprecated.
Definition: ApiBase.php:463
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiBase.php:397
string $mModuleName
Definition: ApiBase.php:273
needsToken()
Returns the token type this module requires in order to execute.
Definition: ApiBase.php:495
IContextSource $context
getErrorFromStatus( $status, &$extraData=null)
Get error (as code, string) from a Status object.
Definition: ApiBase.php:2762
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition: ApiBase.php:2162
getFinalSummary()
Get final module summary.
Definition: ApiBase.php:2218
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition: ApiBase.php:884
const PARAM_ISMULTI_LIMIT1
(integer) Maximum number of values, for normal users.
Definition: ApiBase.php:208
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:1818
const PARAM_MAX_CHARS
(integer) Maximum length of a string in characters (unicode codepoints).
Definition: ApiBase.php:227
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:46
dieUsage( $description, $errorCode, $httpRespCode=0, $extradata=null)
Throw an ApiUsageException, which will (if uncaught) call the main module&#39;s error handler and die wit...
Definition: ApiBase.php:2742
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
static getCanonicalName( $name, $validate='valid')
Given unvalidated user input, return a canonical username, or false if the username is invalid...
Definition: User.php:1229
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition: ApiBase.php:131
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:1499
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
getSummaryMessage()
Return the summary message.
Definition: ApiBase.php:2188
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:172
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:1107
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:3042
const PARAM_ISMULTI_LIMIT2
(integer) Maximum number of values, for users with the apihighimits right.
Definition: ApiBase.php:215
parseMsg( $error)
Return the error message related to a certain array.
Definition: ApiBase.php:2972
profileOut()
Definition: ApiBase.php:2684
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:2493
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition: ApiBase.php:967
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE &#39;integer&#39;, enforce PARAM_MIN and PARAM_MAX?
Definition: ApiBase.php:117
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:2140
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getModulePath()
Get the path to this module.
Definition: ApiBase.php:603
getContinuationManager()
Get the continuation manager.
Definition: ApiBase.php:699
setContinuationManager(ApiContinuationManager $manager=null)
Set the continuation manager.
Definition: ApiBase.php:713
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:258
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition: ApiBase.php:1471
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition: ApiBase.php:284
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is &#39;submodule&#39;, map parameter values to submodule paths.
Definition: ApiBase.php:165
dieUsageMsgOrDebug( $error)
Will only set a warning instead of failing if the global $wgDebugAPI is set to true.
Definition: ApiBase.php:3015
getContext()
Get the base IContextSource object.
const IGNORE_USER_RIGHTS
Definition: User.php:83
$params
This is the main API class, used for both external and internal processing.
Definition: ApiMain.php:41
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2041
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:473
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition: ApiBase.php:1148
setContext(IContextSource $context)
Set the language and the title from a context object.
Definition: Message.php:712
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:949
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition: ApiBase.php:737
getModuleName()
Get the name of the module being executed by this instance.
Definition: ApiBase.php:539
$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:96
getModuleFromPath( $path)
Get a module from its module path.
Definition: ApiBase.php:621
string $mModulePrefix
Definition: ApiBase.php:273
const TYPE_AUTO
Definition: Block.php:86
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition: ApiBase.php:2615
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable & $code
Definition: hooks.txt:798
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:939
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter...
Definition: ApiBase.php:124
setWarning( $warning)
Definition: ApiBase.php:2723
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition: ApiBase.php:1843
const PARAM_SENSITIVE
(boolean) Is the parameter sensitive? Note &#39;password&#39;-type fields are always sensitive regardless of ...
Definition: ApiBase.php:193
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:256
const PARAM_TEMPLATE_VARS
(array) Indicate that this is a templated parameter, and specify replacements.
Definition: ApiBase.php:245
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition: ApiBase.php:322
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition: ApiBase.php:1764
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:163
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:1042
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition: ApiBase.php:748
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition: ApiBase.php:1996
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition: ApiBase.php:454
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition: ApiBase.php:524
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:1943
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:2123
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition: ApiBase.php:547
dieReadOnly()
Helper function for readonly errors.
Definition: ApiBase.php:2064
const RE_IP_BYTE
Definition: IP.php:29
getDescription()
Returns the description string for this module.
Definition: ApiBase.php:2634
$parent
Definition: pageupdater.txt:71
static newFromSpecifier( $value)
Transform a MessageSpecifier or a primitive value used interchangeably with specifiers (a message key...
Definition: Message.php:415
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:2245
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1896
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$line
Definition: cdb.php:59
const PARAM_DEPRECATED_VALUES
(array) When PARAM_TYPE is an array, this indicates which of the values are deprecated.
Definition: ApiBase.php:202
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition: ApiBase.php:51
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:2152
getExamples()
Returns usage examples for this module.
Definition: ApiBase.php:2670
const ALL_DEFAULT_STRING
Definition: ApiBase.php:249
profileIn()
Definition: ApiBase.php:2677
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module&#39;s error handler and die wit...
Definition: ApiBase.php:2012
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:302
$mParamCache
Definition: ApiBase.php:275
Extension of RawMessage implementing IApiMessage.
This abstract class implements many basic API functions, and is the base of all API classes...
Definition: ApiBase.php:37
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:508
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is &#39;namespace&#39;, include these as additional possible values.
Definition: ApiBase.php:186
validateToken( $token, array $params)
Validate the supplied token.
Definition: ApiBase.php:1670
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition: ApiBase.php:1749
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition: ApiBase.php:105
array null bool $mModuleSource
Definition: ApiBase.php:277
const DB_REPLICA
Definition: defines.php:25
getParamDescription()
Returns an array of parameter descriptions.
Definition: ApiBase.php:2650
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:523
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiBase.php:350
profileDBIn()
Definition: ApiBase.php:2698
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:997
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE &#39;integer&#39; and &#39;limit&#39;.
Definition: ApiBase.php:99
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:588
$ext
Definition: router.php:55
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition: ApiBase.php:423
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:2201
Definition: Block.php:27
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:2681
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'])
Add warnings and/or errors from a Status.
Definition: ApiBase.php:1955
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition: ApiBase.php:2299
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition: ApiBase.php:2080
static parseMsgInternal( $error)
Definition: ApiBase.php:2951
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition: ApiBase.php:102
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
static listParam(array $list, $type='text')
Definition: Message.php:1114
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
Definition: ApiMessage.php:40
static isExternal( $username)
Tells whether the username is external or not.
isMain()
Returns true if this module is the main module ($this === $this->mMainModule), false otherwise...
Definition: ApiBase.php:564
isWriteMode()
Indicates whether this module requires write mode.
Definition: ApiBase.php:446
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:273
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiBase.php:413