MediaWiki REL1_28
ApiBase.php
Go to the documentation of this file.
1<?php
39abstract class ApiBase extends ContextSource {
40
50 const PARAM_DFLT = 0;
51
53 const PARAM_ISMULTI = 1;
54
88 const PARAM_TYPE = 2;
89
91 const PARAM_MAX = 3;
92
97 const PARAM_MAX2 = 4;
98
100 const PARAM_MIN = 5;
101
104
107
112 const PARAM_REQUIRED = 8;
113
119
125 const PARAM_HELP_MSG = 10;
126
133
143
150
158
166
173
179 const PARAM_SENSITIVE = 17;
180
184 const LIMIT_BIG1 = 500;
186 const LIMIT_BIG2 = 5000;
188 const LIMIT_SML1 = 50;
190 const LIMIT_SML2 = 500;
191
198
200 private static $extensionInfo = null;
201
206 private $mSlaveDB = null;
207 private $mParamCache = [];
209 private $mModuleSource = false;
210
216 public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
217 $this->mMainModule = $mainModule;
218 $this->mModuleName = $moduleName;
219 $this->mModulePrefix = $modulePrefix;
220
221 if ( !$this->isMain() ) {
222 $this->setContext( $mainModule->getContext() );
223 }
224 }
225
226 /************************************************************************/
247 abstract public function execute();
248
254 public function getModuleManager() {
255 return null;
256 }
257
267 public function getCustomPrinter() {
268 return null;
269 }
270
282 protected function getExamplesMessages() {
283 // Fall back to old non-localised method
284 $ret = [];
285
286 $examples = $this->getExamples();
287 if ( $examples ) {
288 if ( !is_array( $examples ) ) {
289 $examples = [ $examples ];
290 } elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
291 array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
292 !preg_match( '/^\s*api\.php\?/', $examples[0] )
293 ) {
294 // Fix up the ugly "even numbered elements are description, odd
295 // numbered elemts are the link" format (see doc for self::getExamples)
296 $tmp = [];
297 $examplesCount = count( $examples );
298 for ( $i = 0; $i < $examplesCount; $i += 2 ) {
299 $tmp[$examples[$i + 1]] = $examples[$i];
300 }
301 $examples = $tmp;
302 }
303
304 foreach ( $examples as $k => $v ) {
305 if ( is_numeric( $k ) ) {
306 $qs = $v;
307 $msg = '';
308 } else {
309 $qs = $k;
310 $msg = self::escapeWikiText( $v );
311 if ( is_array( $msg ) ) {
312 $msg = implode( ' ', $msg );
313 }
314 }
315
316 $qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
317 $ret[$qs] = $this->msg( 'api-help-fallback-example', [ $msg ] );
318 }
319 }
320
321 return $ret;
322 }
323
329 public function getHelpUrls() {
330 return [];
331 }
332
345 protected function getAllowedParams( /* $flags = 0 */ ) {
346 // int $flags is not declared because it causes "Strict standards"
347 // warning. Most derived classes do not implement it.
348 return [];
349 }
350
355 public function shouldCheckMaxlag() {
356 return true;
357 }
358
363 public function isReadMode() {
364 return true;
365 }
366
371 public function isWriteMode() {
372 return false;
373 }
374
379 public function mustBePosted() {
380 return $this->needsToken() !== false;
381 }
382
388 public function isDeprecated() {
389 return false;
390 }
391
398 public function isInternal() {
399 return false;
400 }
401
420 public function needsToken() {
421 return false;
422 }
423
433 protected function getWebUITokenSalt( array $params ) {
434 return null;
435 }
436
449 public function getConditionalRequestData( $condition ) {
450 return null;
451 }
452
455 /************************************************************************/
464 public function getModuleName() {
465 return $this->mModuleName;
466 }
467
472 public function getModulePrefix() {
474 }
475
480 public function getMain() {
481 return $this->mMainModule;
482 }
483
489 public function isMain() {
490 return $this === $this->mMainModule;
491 }
492
498 public function getParent() {
499 return $this->isMain() ? null : $this->getMain();
500 }
501
512 public function lacksSameOriginSecurity() {
513 // Main module has this method overridden
514 // Safety - avoid infinite loop:
515 if ( $this->isMain() ) {
516 ApiBase::dieDebug( __METHOD__, 'base method was called on main module.' );
517 }
518
519 return $this->getMain()->lacksSameOriginSecurity();
520 }
521
528 public function getModulePath() {
529 if ( $this->isMain() ) {
530 return 'main';
531 } elseif ( $this->getParent()->isMain() ) {
532 return $this->getModuleName();
533 } else {
534 return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
535 }
536 }
537
546 public function getModuleFromPath( $path ) {
547 $module = $this->getMain();
548 if ( $path === 'main' ) {
549 return $module;
550 }
551
552 $parts = explode( '+', $path );
553 if ( count( $parts ) === 1 ) {
554 // In case the '+' was typed into URL, it resolves as a space
555 $parts = explode( ' ', $path );
556 }
557
558 $count = count( $parts );
559 for ( $i = 0; $i < $count; $i++ ) {
560 $parent = $module;
561 $manager = $parent->getModuleManager();
562 if ( $manager === null ) {
563 $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
564 $this->dieUsage( "The module \"$errorPath\" has no submodules", 'badmodule' );
565 }
566 $module = $manager->getModule( $parts[$i] );
567
568 if ( $module === null ) {
569 $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
570 $this->dieUsage(
571 "The module \"$errorPath\" does not have a submodule \"{$parts[$i]}\"",
572 'badmodule'
573 );
574 }
575 }
576
577 return $module;
578 }
579
584 public function getResult() {
585 // Main module has getResult() method overridden
586 // Safety - avoid infinite loop:
587 if ( $this->isMain() ) {
588 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
589 }
590
591 return $this->getMain()->getResult();
592 }
593
598 public function getErrorFormatter() {
599 // Main module has getErrorFormatter() method overridden
600 // Safety - avoid infinite loop:
601 if ( $this->isMain() ) {
602 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
603 }
604
605 return $this->getMain()->getErrorFormatter();
606 }
607
612 protected function getDB() {
613 if ( !isset( $this->mSlaveDB ) ) {
614 $this->mSlaveDB = wfGetDB( DB_REPLICA, 'api' );
615 }
616
617 return $this->mSlaveDB;
618 }
619
624 public function getContinuationManager() {
625 // Main module has getContinuationManager() method overridden
626 // Safety - avoid infinite loop:
627 if ( $this->isMain() ) {
628 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
629 }
630
631 return $this->getMain()->getContinuationManager();
632 }
633
638 public function setContinuationManager( $manager ) {
639 // Main module has setContinuationManager() method overridden
640 // Safety - avoid infinite loop:
641 if ( $this->isMain() ) {
642 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
643 }
644
645 $this->getMain()->setContinuationManager( $manager );
646 }
647
650 /************************************************************************/
663 return null;
664 }
665
672 public function encodeParamName( $paramName ) {
673 return $this->mModulePrefix . $paramName;
674 }
675
685 public function extractRequestParams( $parseLimit = true ) {
686 // Cache parameters, for performance and to avoid bug 24564.
687 if ( !isset( $this->mParamCache[$parseLimit] ) ) {
688 $params = $this->getFinalParams();
689 $results = [];
690
691 if ( $params ) { // getFinalParams() can return false
692 foreach ( $params as $paramName => $paramSettings ) {
693 $results[$paramName] = $this->getParameterFromSettings(
694 $paramName, $paramSettings, $parseLimit );
695 }
696 }
697 $this->mParamCache[$parseLimit] = $results;
698 }
699
700 return $this->mParamCache[$parseLimit];
701 }
702
709 protected function getParameter( $paramName, $parseLimit = true ) {
710 $paramSettings = $this->getFinalParams()[$paramName];
711
712 return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
713 }
714
721 public function requireOnlyOneParameter( $params, $required /*...*/ ) {
722 $required = func_get_args();
723 array_shift( $required );
724 $p = $this->getModulePrefix();
725
726 $intersection = array_intersect( array_keys( array_filter( $params,
727 [ $this, 'parameterNotEmpty' ] ) ), $required );
728
729 if ( count( $intersection ) > 1 ) {
730 $this->dieUsage(
731 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
732 'invalidparammix' );
733 } elseif ( count( $intersection ) == 0 ) {
734 $this->dieUsage(
735 "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required',
736 'missingparam'
737 );
738 }
739 }
740
747 public function requireMaxOneParameter( $params, $required /*...*/ ) {
748 $required = func_get_args();
749 array_shift( $required );
750 $p = $this->getModulePrefix();
751
752 $intersection = array_intersect( array_keys( array_filter( $params,
753 [ $this, 'parameterNotEmpty' ] ) ), $required );
754
755 if ( count( $intersection ) > 1 ) {
756 $this->dieUsage(
757 "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together',
758 'invalidparammix'
759 );
760 }
761 }
762
770 public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
771 $required = func_get_args();
772 array_shift( $required );
773 $p = $this->getModulePrefix();
774
775 $intersection = array_intersect(
776 array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
777 $required
778 );
779
780 if ( count( $intersection ) == 0 ) {
781 $this->dieUsage( "At least one of the parameters {$p}" .
782 implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" );
783 }
784 }
785
793 public function requirePostedParameters( $params, $prefix = 'prefix' ) {
794 // Skip if $wgDebugAPI is set or we're in internal mode
795 if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
796 return;
797 }
798
799 $queryValues = $this->getRequest()->getQueryValues();
800 $badParams = [];
801 foreach ( $params as $param ) {
802 if ( $prefix !== 'noprefix' ) {
803 $param = $this->encodeParamName( $param );
804 }
805 if ( array_key_exists( $param, $queryValues ) ) {
806 $badParams[] = $param;
807 }
808 }
809
810 if ( $badParams ) {
811 $this->dieUsage(
812 'The following parameters were found in the query string, but must be in the POST body: '
813 . join( ', ', $badParams ),
814 'mustpostparams'
815 );
816 }
817 }
818
825 private function parameterNotEmpty( $x ) {
826 return !is_null( $x ) && $x !== false;
827 }
828
840 public function getTitleOrPageId( $params, $load = false ) {
841 $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
842
843 $pageObj = null;
844 if ( isset( $params['title'] ) ) {
845 $titleObj = Title::newFromText( $params['title'] );
846 if ( !$titleObj || $titleObj->isExternal() ) {
847 $this->dieUsageMsg( [ 'invalidtitle', $params['title'] ] );
848 }
849 if ( !$titleObj->canExist() ) {
850 $this->dieUsage( "Namespace doesn't allow actual pages", 'pagecannotexist' );
851 }
852 $pageObj = WikiPage::factory( $titleObj );
853 if ( $load !== false ) {
854 $pageObj->loadPageData( $load );
855 }
856 } elseif ( isset( $params['pageid'] ) ) {
857 if ( $load === false ) {
858 $load = 'fromdb';
859 }
860 $pageObj = WikiPage::newFromID( $params['pageid'], $load );
861 if ( !$pageObj ) {
862 $this->dieUsageMsg( [ 'nosuchpageid', $params['pageid'] ] );
863 }
864 }
865
866 return $pageObj;
867 }
868
877 protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
878
879 $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
880
881 switch ( $watchlist ) {
882 case 'watch':
883 return true;
884
885 case 'unwatch':
886 return false;
887
888 case 'preferences':
889 # If the user is already watching, don't bother checking
890 if ( $userWatching ) {
891 return true;
892 }
893 # If no user option was passed, use watchdefault and watchcreations
894 if ( is_null( $userOption ) ) {
895 return $this->getUser()->getBoolOption( 'watchdefault' ) ||
896 $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
897 }
898
899 # Watch the article based on the user preference
900 return $this->getUser()->getBoolOption( $userOption );
901
902 case 'nochange':
903 return $userWatching;
904
905 default:
906 return $userWatching;
907 }
908 }
909
919 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
920 // Some classes may decide to change parameter names
921 $encParamName = $this->encodeParamName( $paramName );
922
923 if ( !is_array( $paramSettings ) ) {
924 $default = $paramSettings;
925 $multi = false;
926 $type = gettype( $paramSettings );
927 $dupes = false;
928 $deprecated = false;
929 $required = false;
930 } else {
931 $default = isset( $paramSettings[self::PARAM_DFLT] )
932 ? $paramSettings[self::PARAM_DFLT]
933 : null;
934 $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
935 ? $paramSettings[self::PARAM_ISMULTI]
936 : false;
937 $type = isset( $paramSettings[self::PARAM_TYPE] )
938 ? $paramSettings[self::PARAM_TYPE]
939 : null;
940 $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
941 ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
942 : false;
943 $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
944 ? $paramSettings[self::PARAM_DEPRECATED]
945 : false;
946 $required = isset( $paramSettings[self::PARAM_REQUIRED] )
947 ? $paramSettings[self::PARAM_REQUIRED]
948 : false;
949
950 // When type is not given, and no choices, the type is the same as $default
951 if ( !isset( $type ) ) {
952 if ( isset( $default ) ) {
953 $type = gettype( $default );
954 } else {
955 $type = 'NULL'; // allow everything
956 }
957 }
958
959 if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
960 $this->getMain()->markParamsSensitive( $encParamName );
961 }
962 }
963
964 if ( $type == 'boolean' ) {
965 if ( isset( $default ) && $default !== false ) {
966 // Having a default value of anything other than 'false' is not allowed
968 __METHOD__,
969 "Boolean param $encParamName's default is set to '$default'. " .
970 'Boolean parameters must default to false.'
971 );
972 }
973
974 $value = $this->getMain()->getCheck( $encParamName );
975 } elseif ( $type == 'upload' ) {
976 if ( isset( $default ) ) {
977 // Having a default value is not allowed
979 __METHOD__,
980 "File upload param $encParamName's default is set to " .
981 "'$default'. File upload parameters may not have a default." );
982 }
983 if ( $multi ) {
984 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
985 }
986 $value = $this->getMain()->getUpload( $encParamName );
987 if ( !$value->exists() ) {
988 // This will get the value without trying to normalize it
989 // (because trying to normalize a large binary file
990 // accidentally uploaded as a field fails spectacularly)
991 $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
992 if ( $value !== null ) {
993 $this->dieUsage(
994 "File upload param $encParamName is not a file upload; " .
995 'be sure to use multipart/form-data for your POST and include ' .
996 'a filename in the Content-Disposition header.',
997 "badupload_{$encParamName}"
998 );
999 }
1000 }
1001 } else {
1002 $value = $this->getMain()->getVal( $encParamName, $default );
1003
1004 if ( isset( $value ) && $type == 'namespace' ) {
1005 $type = MWNamespace::getValidNamespaces();
1006 }
1007 if ( isset( $value ) && $type == 'submodule' ) {
1008 if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1009 $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1010 } else {
1011 $type = $this->getModuleManager()->getNames( $paramName );
1012 }
1013 }
1014
1015 $request = $this->getMain()->getRequest();
1016 $rawValue = $request->getRawVal( $encParamName );
1017 if ( $rawValue === null ) {
1018 $rawValue = $default;
1019 }
1020
1021 // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1022 if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1023 if ( $multi ) {
1024 // This loses the potential $wgContLang->checkTitleEncoding() transformation
1025 // done by WebRequest for $_GET. Let's call that a feature.
1026 $value = join( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1027 } else {
1028 $this->dieUsage(
1029 "U+001F multi-value separation may only be used for multi-valued parameters.",
1030 'badvalue_notmultivalue'
1031 );
1032 }
1033 }
1034
1035 // Check for NFC normalization, and warn
1036 if ( $rawValue !== $value ) {
1037 $this->handleParamNormalization( $paramName, $value, $rawValue );
1038 }
1039 }
1040
1041 if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1042 $value = $this->parseMultiValue(
1043 $encParamName,
1044 $value,
1045 $multi,
1046 is_array( $type ) ? $type : null
1047 );
1048 }
1049
1050 // More validation only when choices were not given
1051 // choices were validated in parseMultiValue()
1052 if ( isset( $value ) ) {
1053 if ( !is_array( $type ) ) {
1054 switch ( $type ) {
1055 case 'NULL': // nothing to do
1056 break;
1057 case 'string':
1058 case 'text':
1059 case 'password':
1060 if ( $required && $value === '' ) {
1061 $this->dieUsageMsg( [ 'missingparam', $paramName ] );
1062 }
1063 break;
1064 case 'integer': // Force everything using intval() and optionally validate limits
1065 $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
1066 $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
1067 $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
1068 ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
1069
1070 if ( is_array( $value ) ) {
1071 $value = array_map( 'intval', $value );
1072 if ( !is_null( $min ) || !is_null( $max ) ) {
1073 foreach ( $value as &$v ) {
1074 $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1075 }
1076 }
1077 } else {
1078 $value = intval( $value );
1079 if ( !is_null( $min ) || !is_null( $max ) ) {
1080 $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1081 }
1082 }
1083 break;
1084 case 'limit':
1085 if ( !$parseLimit ) {
1086 // Don't do any validation whatsoever
1087 break;
1088 }
1089 if ( !isset( $paramSettings[self::PARAM_MAX] )
1090 || !isset( $paramSettings[self::PARAM_MAX2] )
1091 ) {
1093 __METHOD__,
1094 "MAX1 or MAX2 are not defined for the limit $encParamName"
1095 );
1096 }
1097 if ( $multi ) {
1098 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1099 }
1100 $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
1101 if ( $value == 'max' ) {
1102 $value = $this->getMain()->canApiHighLimits()
1103 ? $paramSettings[self::PARAM_MAX2]
1104 : $paramSettings[self::PARAM_MAX];
1105 $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1106 } else {
1107 $value = intval( $value );
1108 $this->validateLimit(
1109 $paramName,
1110 $value,
1111 $min,
1112 $paramSettings[self::PARAM_MAX],
1113 $paramSettings[self::PARAM_MAX2]
1114 );
1115 }
1116 break;
1117 case 'boolean':
1118 if ( $multi ) {
1119 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1120 }
1121 break;
1122 case 'timestamp':
1123 if ( is_array( $value ) ) {
1124 foreach ( $value as $key => $val ) {
1125 $value[$key] = $this->validateTimestamp( $val, $encParamName );
1126 }
1127 } else {
1128 $value = $this->validateTimestamp( $value, $encParamName );
1129 }
1130 break;
1131 case 'user':
1132 if ( is_array( $value ) ) {
1133 foreach ( $value as $key => $val ) {
1134 $value[$key] = $this->validateUser( $val, $encParamName );
1135 }
1136 } else {
1137 $value = $this->validateUser( $value, $encParamName );
1138 }
1139 break;
1140 case 'upload': // nothing to do
1141 break;
1142 case 'tags':
1143 // If change tagging was requested, check that the tags are valid.
1144 if ( !is_array( $value ) && !$multi ) {
1145 $value = [ $value ];
1146 }
1148 if ( !$tagsStatus->isGood() ) {
1149 $this->dieStatus( $tagsStatus );
1150 }
1151 break;
1152 default:
1153 ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1154 }
1155 }
1156
1157 // Throw out duplicates if requested
1158 if ( !$dupes && is_array( $value ) ) {
1159 $value = array_unique( $value );
1160 }
1161
1162 // Set a warning if a deprecated parameter has been passed
1163 if ( $deprecated && $value !== false ) {
1164 $this->setWarning( "The $encParamName parameter has been deprecated." );
1165
1166 $feature = $encParamName;
1167 $m = $this;
1168 while ( !$m->isMain() ) {
1169 $p = $m->getParent();
1170 $name = $m->getModuleName();
1171 $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1172 $feature = "{$param}={$name}&{$feature}";
1173 $m = $p;
1174 }
1175 $this->logFeatureUsage( $feature );
1176 }
1177 } elseif ( $required ) {
1178 $this->dieUsageMsg( [ 'missingparam', $paramName ] );
1179 }
1180
1181 return $value;
1182 }
1183
1191 protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1192 $encParamName = $this->encodeParamName( $paramName );
1193 $this->setWarning(
1194 "The value passed for '$encParamName' contains invalid or non-normalized data. "
1195 . 'Textual data should be valid, NFC-normalized Unicode without '
1196 . 'C0 control characters other than HT (\\t), LF (\\n), and CR (\\r).'
1197 );
1198 }
1199
1207 protected function explodeMultiValue( $value, $limit ) {
1208 if ( substr( $value, 0, 1 ) === "\x1f" ) {
1209 $sep = "\x1f";
1210 $value = substr( $value, 1 );
1211 } else {
1212 $sep = '|';
1213 }
1214
1215 return explode( $sep, $value, $limit );
1216 }
1217
1231 protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues ) {
1232 if ( ( trim( $value ) === '' || trim( $value ) === "\x1f" ) && $allowMultiple ) {
1233 return [];
1234 }
1235
1236 // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1237 // because it unstubs $wgUser
1238 $valuesList = $this->explodeMultiValue( $value, self::LIMIT_SML2 + 1 );
1239 $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
1242
1243 if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
1244 $this->logFeatureUsage( "too-many-$valueName-for-{$this->getModulePath()}" );
1245 $this->setWarning( "Too many values supplied for parameter '$valueName': " .
1246 "the limit is $sizeLimit" );
1247 }
1248
1249 if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1250 // Bug 33482 - Allow entries with | in them for non-multiple values
1251 if ( in_array( $value, $allowedValues, true ) ) {
1252 return $value;
1253 }
1254
1255 $possibleValues = is_array( $allowedValues )
1256 ? "of '" . implode( "', '", $allowedValues ) . "'"
1257 : '';
1258 $this->dieUsage(
1259 "Only one $possibleValues is allowed for parameter '$valueName'",
1260 "multival_$valueName"
1261 );
1262 }
1263
1264 if ( is_array( $allowedValues ) ) {
1265 // Check for unknown values
1266 $unknown = array_diff( $valuesList, $allowedValues );
1267 if ( count( $unknown ) ) {
1268 if ( $allowMultiple ) {
1269 $s = count( $unknown ) > 1 ? 's' : '';
1270 $vals = implode( ', ', $unknown );
1271 $this->setWarning( "Unrecognized value$s for parameter '$valueName': $vals" );
1272 } else {
1273 $this->dieUsage(
1274 "Unrecognized value for parameter '$valueName': {$valuesList[0]}",
1275 "unknown_$valueName"
1276 );
1277 }
1278 }
1279 // Now throw them out
1280 $valuesList = array_intersect( $valuesList, $allowedValues );
1281 }
1282
1283 return $allowMultiple ? $valuesList : $valuesList[0];
1284 }
1285
1296 protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1297 $enforceLimits = false
1298 ) {
1299 if ( !is_null( $min ) && $value < $min ) {
1300 $msg = $this->encodeParamName( $paramName ) . " may not be less than $min (set to $value)";
1301 $this->warnOrDie( $msg, $enforceLimits );
1302 $value = $min;
1303 }
1304
1305 // Minimum is always validated, whereas maximum is checked only if not
1306 // running in internal call mode
1307 if ( $this->getMain()->isInternalMode() ) {
1308 return;
1309 }
1310
1311 // Optimization: do not check user's bot status unless really needed -- skips db query
1312 // assumes $botMax >= $max
1313 if ( !is_null( $max ) && $value > $max ) {
1314 if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1315 if ( $value > $botMax ) {
1316 $msg = $this->encodeParamName( $paramName ) .
1317 " may not be over $botMax (set to $value) for bots or sysops";
1318 $this->warnOrDie( $msg, $enforceLimits );
1319 $value = $botMax;
1320 }
1321 } else {
1322 $msg = $this->encodeParamName( $paramName ) . " may not be over $max (set to $value) for users";
1323 $this->warnOrDie( $msg, $enforceLimits );
1324 $value = $max;
1325 }
1326 }
1327 }
1328
1335 protected function validateTimestamp( $value, $encParamName ) {
1336 // Confusing synonyms for the current time accepted by wfTimestamp()
1337 // (wfTimestamp() also accepts various non-strings and the string of 14
1338 // ASCII NUL bytes, but those can't get here)
1339 if ( !$value ) {
1340 $this->logFeatureUsage( 'unclear-"now"-timestamp' );
1341 $this->setWarning(
1342 "Passing '$value' for timestamp parameter $encParamName has been deprecated." .
1343 ' If for some reason you need to explicitly specify the current time without' .
1344 ' calculating it client-side, use "now".'
1345 );
1346 return wfTimestamp( TS_MW );
1347 }
1348
1349 // Explicit synonym for the current time
1350 if ( $value === 'now' ) {
1351 return wfTimestamp( TS_MW );
1352 }
1353
1354 $unixTimestamp = wfTimestamp( TS_UNIX, $value );
1355 if ( $unixTimestamp === false ) {
1356 $this->dieUsage(
1357 "Invalid value '$value' for timestamp parameter $encParamName",
1358 "badtimestamp_{$encParamName}"
1359 );
1360 }
1361
1362 return wfTimestamp( TS_MW, $unixTimestamp );
1363 }
1364
1374 final public function validateToken( $token, array $params ) {
1375 $tokenType = $this->needsToken();
1377 if ( !isset( $salts[$tokenType] ) ) {
1378 throw new MWException(
1379 "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1380 'without registering it'
1381 );
1382 }
1383
1384 $tokenObj = ApiQueryTokens::getToken(
1385 $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1386 );
1387 if ( $tokenObj->match( $token ) ) {
1388 return true;
1389 }
1390
1391 $webUiSalt = $this->getWebUITokenSalt( $params );
1392 if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1393 $token,
1394 $webUiSalt,
1395 $this->getRequest()
1396 ) ) {
1397 return true;
1398 }
1399
1400 return false;
1401 }
1402
1409 private function validateUser( $value, $encParamName ) {
1410 $title = Title::makeTitleSafe( NS_USER, $value );
1411 if ( $title === null || $title->hasFragment() ) {
1412 $this->dieUsage(
1413 "Invalid value '$value' for user parameter $encParamName",
1414 "baduser_{$encParamName}"
1415 );
1416 }
1417
1418 return $title->getText();
1419 }
1420
1423 /************************************************************************/
1434 protected function setWatch( $watch, $titleObj, $userOption = null ) {
1435 $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1436 if ( $value === null ) {
1437 return;
1438 }
1439
1440 WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1441 }
1442
1449 public static function truncateArray( &$arr, $limit ) {
1450 $modified = false;
1451 while ( count( $arr ) > $limit ) {
1452 array_pop( $arr );
1453 $modified = true;
1454 }
1455
1456 return $modified;
1457 }
1458
1465 public function getWatchlistUser( $params ) {
1466 if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1467 $user = User::newFromName( $params['owner'], false );
1468 if ( !( $user && $user->getId() ) ) {
1469 $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' );
1470 }
1471 $token = $user->getOption( 'watchlisttoken' );
1472 if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1473 $this->dieUsage(
1474 'Incorrect watchlist token provided -- please set a correct token in Special:Preferences',
1475 'bad_wltoken'
1476 );
1477 }
1478 } else {
1479 if ( !$this->getUser()->isLoggedIn() ) {
1480 $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' );
1481 }
1482 if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) {
1483 $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' );
1484 }
1485 $user = $this->getUser();
1486 }
1487
1488 return $user;
1489 }
1490
1498 private static function escapeWikiText( $v ) {
1499 if ( is_array( $v ) ) {
1500 return array_map( 'self::escapeWikiText', $v );
1501 } else {
1502 return strtr( $v, [
1503 '__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
1504 '[[Category:' => '[[:Category:',
1505 '[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
1506 ] );
1507 }
1508 }
1509
1522 public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1523 if ( is_string( $msg ) ) {
1524 $msg = wfMessage( $msg );
1525 } elseif ( is_array( $msg ) ) {
1526 $msg = call_user_func_array( 'wfMessage', $msg );
1527 }
1528 if ( !$msg instanceof Message ) {
1529 return null;
1530 }
1531
1532 $msg->setContext( $context );
1533 if ( $params ) {
1534 $msg->params( $params );
1535 }
1536
1537 return $msg;
1538 }
1539
1542 /************************************************************************/
1554 public function setWarning( $warning ) {
1555 $msg = new ApiRawMessage( $warning, 'warning' );
1556 $this->getErrorFormatter()->addWarning( $this->getModuleName(), $msg );
1557 }
1558
1565 private function warnOrDie( $msg, $enforceLimits = false ) {
1566 if ( $enforceLimits ) {
1567 $this->dieUsage( $msg, 'integeroutofrange' );
1568 }
1569
1570 $this->setWarning( $msg );
1571 }
1572
1585 public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
1586 throw new UsageException(
1587 $description,
1588 $this->encodeParamName( $errorCode ),
1589 $httpRespCode,
1590 $extradata
1591 );
1592 }
1593
1602 public function dieBlocked( Block $block ) {
1603 // Die using the appropriate message depending on block type
1604 if ( $block->getType() == Block::TYPE_AUTO ) {
1605 $this->dieUsage(
1606 'Your IP address has been blocked automatically, because it was used by a blocked user',
1607 'autoblocked',
1608 0,
1609 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1610 );
1611 } else {
1612 $this->dieUsage(
1613 'You have been blocked from editing',
1614 'blocked',
1615 0,
1616 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1617 );
1618 }
1619 }
1620
1630 public function getErrorFromStatus( $status, &$extraData = null ) {
1631 if ( $status->isGood() ) {
1632 throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
1633 }
1634
1635 $errors = $status->getErrorsByType( 'error' );
1636 if ( !$errors ) {
1637 // No errors? Assume the warnings should be treated as errors
1638 $errors = $status->getErrorsByType( 'warning' );
1639 }
1640 if ( !$errors ) {
1641 // Still no errors? Punt
1642 $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
1643 }
1644
1645 // Cannot use dieUsageMsg() because extensions might return custom
1646 // error messages.
1647 if ( $errors[0]['message'] instanceof Message ) {
1648 $msg = $errors[0]['message'];
1649 if ( $msg instanceof IApiMessage ) {
1650 $extraData = $msg->getApiData();
1651 $code = $msg->getApiCode();
1652 } else {
1653 $code = $msg->getKey();
1654 }
1655 } else {
1656 $code = $errors[0]['message'];
1657 $msg = wfMessage( $code, $errors[0]['params'] );
1658 }
1659 if ( isset( ApiBase::$messageMap[$code] ) ) {
1660 // Translate message to code, for backwards compatibility
1661 $code = ApiBase::$messageMap[$code]['code'];
1662 }
1663
1664 return [ $code, $msg->inLanguage( 'en' )->useDatabase( false )->plain() ];
1665 }
1666
1674 public function dieStatus( $status ) {
1675 $extraData = null;
1676 list( $code, $msg ) = $this->getErrorFromStatus( $status, $extraData );
1677 $this->dieUsage( $msg, $code, 0, $extraData );
1678 }
1679
1680 // @codingStandardsIgnoreStart Allow long lines. Cannot split these.
1684 public static $messageMap = [
1685 // This one MUST be present, or dieUsageMsg() will recurse infinitely
1686 'unknownerror' => [ 'code' => 'unknownerror', 'info' => "Unknown error: \"\$1\"" ],
1687 'unknownerror-nocode' => [ 'code' => 'unknownerror', 'info' => 'Unknown error' ],
1688
1689 // Messages from Title::getUserPermissionsErrors()
1690 'ns-specialprotected' => [
1691 'code' => 'unsupportednamespace',
1692 'info' => "Pages in the Special namespace can't be edited"
1693 ],
1694 'protectedinterface' => [
1695 'code' => 'protectednamespace-interface',
1696 'info' => "You're not allowed to edit interface messages"
1697 ],
1698 'namespaceprotected' => [
1699 'code' => 'protectednamespace',
1700 'info' => "You're not allowed to edit pages in the \"\$1\" namespace"
1701 ],
1702 'customcssprotected' => [
1703 'code' => 'customcssprotected',
1704 'info' => "You're not allowed to edit custom CSS pages"
1705 ],
1706 'customjsprotected' => [
1707 'code' => 'customjsprotected',
1708 'info' => "You're not allowed to edit custom JavaScript pages"
1709 ],
1710 'cascadeprotected' => [
1711 'code' => 'cascadeprotected',
1712 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page"
1713 ],
1714 'protectedpagetext' => [
1715 'code' => 'protectedpage',
1716 'info' => "The \"\$1\" right is required to edit this page"
1717 ],
1718 'protect-cantedit' => [
1719 'code' => 'cantedit',
1720 'info' => "You can't protect this page because you can't edit it"
1721 ],
1722 'deleteprotected' => [
1723 'code' => 'cantedit',
1724 'info' => "You can't delete this page because it has been protected"
1725 ],
1726 'badaccess-group0' => [
1727 'code' => 'permissiondenied',
1728 'info' => 'Permission denied'
1729 ], // Generic permission denied message
1730 'badaccess-groups' => [
1731 'code' => 'permissiondenied',
1732 'info' => 'Permission denied'
1733 ],
1734 'titleprotected' => [
1735 'code' => 'protectedtitle',
1736 'info' => 'This title has been protected from creation'
1737 ],
1738 'nocreate-loggedin' => [
1739 'code' => 'cantcreate',
1740 'info' => "You don't have permission to create new pages"
1741 ],
1742 'nocreatetext' => [
1743 'code' => 'cantcreate-anon',
1744 'info' => "Anonymous users can't create new pages"
1745 ],
1746 'movenologintext' => [
1747 'code' => 'cantmove-anon',
1748 'info' => "Anonymous users can't move pages"
1749 ],
1750 'movenotallowed' => [
1751 'code' => 'cantmove',
1752 'info' => "You don't have permission to move pages"
1753 ],
1754 'confirmedittext' => [
1755 'code' => 'confirmemail',
1756 'info' => 'You must confirm your email address before you can edit'
1757 ],
1758 'blockedtext' => [
1759 'code' => 'blocked',
1760 'info' => 'You have been blocked from editing'
1761 ],
1762 'autoblockedtext' => [
1763 'code' => 'autoblocked',
1764 'info' => 'Your IP address has been blocked automatically, because it was used by a blocked user'
1765 ],
1766
1767 // Miscellaneous interface messages
1768 'actionthrottledtext' => [
1769 'code' => 'ratelimited',
1770 'info' => "You've exceeded your rate limit. Please wait some time and try again"
1771 ],
1772 'alreadyrolled' => [
1773 'code' => 'alreadyrolled',
1774 'info' => 'The page you tried to rollback was already rolled back'
1775 ],
1776 'cantrollback' => [
1777 'code' => 'onlyauthor',
1778 'info' => 'The page you tried to rollback only has one author'
1779 ],
1780 'readonlytext' => [
1781 'code' => 'readonly',
1782 'info' => 'The wiki is currently in read-only mode'
1783 ],
1784 'sessionfailure' => [
1785 'code' => 'badtoken',
1786 'info' => 'Invalid token' ],
1787 'cannotdelete' => [
1788 'code' => 'cantdelete',
1789 'info' => "Couldn't delete \"\$1\". Maybe it was deleted already by someone else"
1790 ],
1791 'notanarticle' => [
1792 'code' => 'missingtitle',
1793 'info' => "The page you requested doesn't exist"
1794 ],
1795 'selfmove' => [ 'code' => 'selfmove', 'info' => "Can't move a page to itself"
1796 ],
1797 'immobile_namespace' => [
1798 'code' => 'immobilenamespace',
1799 'info' => 'You tried to move pages from or to a namespace that is protected from moving'
1800 ],
1801 'articleexists' => [
1802 'code' => 'articleexists',
1803 'info' => 'The destination article already exists and is not a redirect to the source article'
1804 ],
1805 'protectedpage' => [
1806 'code' => 'protectedpage',
1807 'info' => "You don't have permission to perform this move"
1808 ],
1809 'hookaborted' => [
1810 'code' => 'hookaborted',
1811 'info' => 'The modification you tried to make was aborted by an extension hook'
1812 ],
1813 'cantmove-titleprotected' => [
1814 'code' => 'protectedtitle',
1815 'info' => 'The destination article has been protected from creation'
1816 ],
1817 'imagenocrossnamespace' => [
1818 'code' => 'nonfilenamespace',
1819 'info' => "Can't move a file to a non-file namespace"
1820 ],
1821 'imagetypemismatch' => [
1822 'code' => 'filetypemismatch',
1823 'info' => "The new file extension doesn't match its type"
1824 ],
1825 // 'badarticleerror' => shouldn't happen
1826 // 'badtitletext' => shouldn't happen
1827 'ip_range_invalid' => [ 'code' => 'invalidrange', 'info' => 'Invalid IP range' ],
1828 'range_block_disabled' => [
1829 'code' => 'rangedisabled',
1830 'info' => 'Blocking IP ranges has been disabled'
1831 ],
1832 'nosuchusershort' => [
1833 'code' => 'nosuchuser',
1834 'info' => "The user you specified doesn't exist"
1835 ],
1836 'badipaddress' => [ 'code' => 'invalidip', 'info' => 'Invalid IP address specified' ],
1837 'ipb_expiry_invalid' => [ 'code' => 'invalidexpiry', 'info' => 'Invalid expiry time' ],
1838 'ipb_already_blocked' => [
1839 'code' => 'alreadyblocked',
1840 'info' => 'The user you tried to block was already blocked'
1841 ],
1842 'ipb_blocked_as_range' => [
1843 'code' => 'blockedasrange',
1844 'info' => "IP address \"\$1\" was blocked as part of range \"\$2\". You can't unblock the IP individually, but you can unblock the range as a whole."
1845 ],
1846 'ipb_cant_unblock' => [
1847 'code' => 'cantunblock',
1848 'info' => 'The block you specified was not found. It may have been unblocked already'
1849 ],
1850 'mailnologin' => [
1851 'code' => 'cantsend',
1852 'info' => 'You are not logged in, you do not have a confirmed email address, or you are not allowed to send email to other users, so you cannot send email'
1853 ],
1854 'ipbblocked' => [
1855 'code' => 'ipbblocked',
1856 'info' => 'You cannot block or unblock users while you are yourself blocked'
1857 ],
1858 'ipbnounblockself' => [
1859 'code' => 'ipbnounblockself',
1860 'info' => 'You are not allowed to unblock yourself'
1861 ],
1862 'usermaildisabled' => [
1863 'code' => 'usermaildisabled',
1864 'info' => 'User email has been disabled'
1865 ],
1866 'blockedemailuser' => [
1867 'code' => 'blockedfrommail',
1868 'info' => 'You have been blocked from sending email'
1869 ],
1870 'notarget' => [
1871 'code' => 'notarget',
1872 'info' => 'You have not specified a valid target for this action'
1873 ],
1874 'noemail' => [
1875 'code' => 'noemail',
1876 'info' => 'The user has not specified a valid email address, or has chosen not to receive email from other users'
1877 ],
1878 'rcpatroldisabled' => [
1879 'code' => 'patroldisabled',
1880 'info' => 'Patrolling is disabled on this wiki'
1881 ],
1882 'markedaspatrollederror-noautopatrol' => [
1883 'code' => 'noautopatrol',
1884 'info' => "You don't have permission to patrol your own changes"
1885 ],
1886 'delete-toobig' => [
1887 'code' => 'bigdelete',
1888 'info' => "You can't delete this page because it has more than \$1 revisions"
1889 ],
1890 'movenotallowedfile' => [
1891 'code' => 'cantmovefile',
1892 'info' => "You don't have permission to move files"
1893 ],
1894 'userrights-no-interwiki' => [
1895 'code' => 'nointerwikiuserrights',
1896 'info' => "You don't have permission to change user rights on other wikis"
1897 ],
1898 'userrights-nodatabase' => [
1899 'code' => 'nosuchdatabase',
1900 'info' => "Database \"\$1\" does not exist or is not local"
1901 ],
1902 'nouserspecified' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1903 'noname' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1904 'summaryrequired' => [ 'code' => 'summaryrequired', 'info' => 'Summary required' ],
1905 'import-rootpage-invalid' => [
1906 'code' => 'import-rootpage-invalid',
1907 'info' => 'Root page is an invalid title'
1908 ],
1909 'import-rootpage-nosubpage' => [
1910 'code' => 'import-rootpage-nosubpage',
1911 'info' => 'Namespace "$1" of the root page does not allow subpages'
1912 ],
1913
1914 // API-specific messages
1915 'readrequired' => [
1916 'code' => 'readapidenied',
1917 'info' => 'You need read permission to use this module'
1918 ],
1919 'writedisabled' => [
1920 'code' => 'noapiwrite',
1921 'info' => "Editing of this wiki through the API is disabled. Make sure the \$wgEnableWriteAPI=true; statement is included in the wiki's LocalSettings.php file"
1922 ],
1923 'writerequired' => [
1924 'code' => 'writeapidenied',
1925 'info' => "You're not allowed to edit this wiki through the API"
1926 ],
1927 'missingparam' => [ 'code' => 'no$1', 'info' => "The \$1 parameter must be set" ],
1928 'invalidtitle' => [ 'code' => 'invalidtitle', 'info' => "Bad title \"\$1\"" ],
1929 'nosuchpageid' => [ 'code' => 'nosuchpageid', 'info' => "There is no page with ID \$1" ],
1930 'nosuchrevid' => [ 'code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1" ],
1931 'nosuchuser' => [ 'code' => 'nosuchuser', 'info' => "User \"\$1\" doesn't exist" ],
1932 'invaliduser' => [ 'code' => 'invaliduser', 'info' => "Invalid username \"\$1\"" ],
1933 'invalidexpiry' => [ 'code' => 'invalidexpiry', 'info' => "Invalid expiry time \"\$1\"" ],
1934 'pastexpiry' => [ 'code' => 'pastexpiry', 'info' => "Expiry time \"\$1\" is in the past" ],
1935 'create-titleexists' => [
1936 'code' => 'create-titleexists',
1937 'info' => "Existing titles can't be protected with 'create'"
1938 ],
1939 'missingtitle-createonly' => [
1940 'code' => 'missingtitle-createonly',
1941 'info' => "Missing titles can only be protected with 'create'"
1942 ],
1943 'cantblock' => [ 'code' => 'cantblock',
1944 'info' => "You don't have permission to block users"
1945 ],
1946 'canthide' => [
1947 'code' => 'canthide',
1948 'info' => "You don't have permission to hide user names from the block log"
1949 ],
1950 'cantblock-email' => [
1951 'code' => 'cantblock-email',
1952 'info' => "You don't have permission to block users from sending email through the wiki"
1953 ],
1954 'unblock-notarget' => [
1955 'code' => 'notarget',
1956 'info' => 'Either the id or the user parameter must be set'
1957 ],
1958 'unblock-idanduser' => [
1959 'code' => 'idanduser',
1960 'info' => "The id and user parameters can't be used together"
1961 ],
1962 'cantunblock' => [
1963 'code' => 'permissiondenied',
1964 'info' => "You don't have permission to unblock users"
1965 ],
1966 'cannotundelete' => [
1967 'code' => 'cantundelete',
1968 'info' => "Couldn't undelete: the requested revisions may not exist, or may have been undeleted already"
1969 ],
1970 'permdenied-undelete' => [
1971 'code' => 'permissiondenied',
1972 'info' => "You don't have permission to restore deleted revisions"
1973 ],
1974 'createonly-exists' => [
1975 'code' => 'articleexists',
1976 'info' => 'The article you tried to create has been created already'
1977 ],
1978 'nocreate-missing' => [
1979 'code' => 'missingtitle',
1980 'info' => "The article you tried to edit doesn't exist"
1981 ],
1982 'cantchangecontentmodel' => [
1983 'code' => 'cantchangecontentmodel',
1984 'info' => "You don't have permission to change the content model of a page"
1985 ],
1986 'nosuchrcid' => [
1987 'code' => 'nosuchrcid',
1988 'info' => "There is no change with rcid \"\$1\""
1989 ],
1990 'nosuchlogid' => [
1991 'code' => 'nosuchlogid',
1992 'info' => "There is no log entry with ID \"\$1\""
1993 ],
1994 'protect-invalidaction' => [
1995 'code' => 'protect-invalidaction',
1996 'info' => "Invalid protection type \"\$1\""
1997 ],
1998 'protect-invalidlevel' => [
1999 'code' => 'protect-invalidlevel',
2000 'info' => "Invalid protection level \"\$1\""
2001 ],
2002 'toofewexpiries' => [
2003 'code' => 'toofewexpiries',
2004 'info' => "\$1 expiry timestamps were provided where \$2 were needed"
2005 ],
2006 'cantimport' => [
2007 'code' => 'cantimport',
2008 'info' => "You don't have permission to import pages"
2009 ],
2010 'cantimport-upload' => [
2011 'code' => 'cantimport-upload',
2012 'info' => "You don't have permission to import uploaded pages"
2013 ],
2014 'importnofile' => [ 'code' => 'nofile', 'info' => "You didn't upload a file" ],
2015 'importuploaderrorsize' => [
2016 'code' => 'filetoobig',
2017 'info' => 'The file you uploaded is bigger than the maximum upload size'
2018 ],
2019 'importuploaderrorpartial' => [
2020 'code' => 'partialupload',
2021 'info' => 'The file was only partially uploaded'
2022 ],
2023 'importuploaderrortemp' => [
2024 'code' => 'notempdir',
2025 'info' => 'The temporary upload directory is missing'
2026 ],
2027 'importcantopen' => [
2028 'code' => 'cantopenfile',
2029 'info' => "Couldn't open the uploaded file"
2030 ],
2031 'import-noarticle' => [
2032 'code' => 'badinterwiki',
2033 'info' => 'Invalid interwiki title specified'
2034 ],
2035 'importbadinterwiki' => [
2036 'code' => 'badinterwiki',
2037 'info' => 'Invalid interwiki title specified'
2038 ],
2039 'import-unknownerror' => [
2040 'code' => 'import-unknownerror',
2041 'info' => "Unknown error on import: \"\$1\""
2042 ],
2043 'cantoverwrite-sharedfile' => [
2044 'code' => 'cantoverwrite-sharedfile',
2045 'info' => 'The target file exists on a shared repository and you do not have permission to override it'
2046 ],
2047 'sharedfile-exists' => [
2048 'code' => 'fileexists-sharedrepo-perm',
2049 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.'
2050 ],
2051 'mustbeposted' => [
2052 'code' => 'mustbeposted',
2053 'info' => "The \$1 module requires a POST request"
2054 ],
2055 'show' => [
2056 'code' => 'show',
2057 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied'
2058 ],
2059 'specialpage-cantexecute' => [
2060 'code' => 'specialpage-cantexecute',
2061 'info' => "You don't have permission to view the results of this special page"
2062 ],
2063 'invalidoldimage' => [
2064 'code' => 'invalidoldimage',
2065 'info' => 'The oldimage parameter has invalid format'
2066 ],
2067 'nodeleteablefile' => [
2068 'code' => 'nodeleteablefile',
2069 'info' => 'No such old version of the file'
2070 ],
2071 'fileexists-forbidden' => [
2072 'code' => 'fileexists-forbidden',
2073 'info' => 'A file with name "$1" already exists, and cannot be overwritten.'
2074 ],
2075 'fileexists-shared-forbidden' => [
2076 'code' => 'fileexists-shared-forbidden',
2077 'info' => 'A file with name "$1" already exists in the shared file repository, and cannot be overwritten.'
2078 ],
2079 'filerevert-badversion' => [
2080 'code' => 'filerevert-badversion',
2081 'info' => 'There is no previous local version of this file with the provided timestamp.'
2082 ],
2083
2084 // ApiEditPage messages
2085 'noimageredirect-anon' => [
2086 'code' => 'noimageredirect-anon',
2087 'info' => "Anonymous users can't create image redirects"
2088 ],
2089 'noimageredirect-logged' => [
2090 'code' => 'noimageredirect',
2091 'info' => "You don't have permission to create image redirects"
2092 ],
2093 'spamdetected' => [
2094 'code' => 'spamdetected',
2095 'info' => "Your edit was refused because it contained a spam fragment: \"\$1\""
2096 ],
2097 'contenttoobig' => [
2098 'code' => 'contenttoobig',
2099 'info' => "The content you supplied exceeds the article size limit of \$1 kilobytes"
2100 ],
2101 'noedit-anon' => [ 'code' => 'noedit-anon', 'info' => "Anonymous users can't edit pages" ],
2102 'noedit' => [ 'code' => 'noedit', 'info' => "You don't have permission to edit pages" ],
2103 'wasdeleted' => [
2104 'code' => 'pagedeleted',
2105 'info' => 'The page has been deleted since you fetched its timestamp'
2106 ],
2107 'blankpage' => [
2108 'code' => 'emptypage',
2109 'info' => 'Creating new, empty pages is not allowed'
2110 ],
2111 'editconflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2112 'hashcheckfailed' => [ 'code' => 'badmd5', 'info' => 'The supplied MD5 hash was incorrect' ],
2113 'missingtext' => [
2114 'code' => 'notext',
2115 'info' => 'One of the text, appendtext, prependtext and undo parameters must be set'
2116 ],
2117 'emptynewsection' => [
2118 'code' => 'emptynewsection',
2119 'info' => 'Creating empty new sections is not possible.'
2120 ],
2121 'revwrongpage' => [
2122 'code' => 'revwrongpage',
2123 'info' => "r\$1 is not a revision of \"\$2\""
2124 ],
2125 'undo-failure' => [
2126 'code' => 'undofailure',
2127 'info' => 'Undo failed due to conflicting intermediate edits'
2128 ],
2129 'content-not-allowed-here' => [
2130 'code' => 'contentnotallowedhere',
2131 'info' => 'Content model "$1" is not allowed at title "$2"'
2132 ],
2133
2134 // Messages from WikiPage::doEit(]
2135 'edit-hook-aborted' => [
2136 'code' => 'edit-hook-aborted',
2137 'info' => 'Your edit was aborted by an ArticleSave hook'
2138 ],
2139 'edit-gone-missing' => [
2140 'code' => 'edit-gone-missing',
2141 'info' => "The page you tried to edit doesn't seem to exist anymore"
2142 ],
2143 'edit-conflict' => [ 'code' => 'editconflict', 'info' => 'Edit conflict detected' ],
2144 'edit-already-exists' => [
2145 'code' => 'edit-already-exists',
2146 'info' => 'It seems the page you tried to create already exist'
2147 ],
2148
2149 // uploadMsgs
2150 'invalid-file-key' => [ 'code' => 'invalid-file-key', 'info' => 'Not a valid file key' ],
2151 'nouploadmodule' => [ 'code' => 'nouploadmodule', 'info' => 'No upload module set' ],
2152 'uploaddisabled' => [
2153 'code' => 'uploaddisabled',
2154 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true'
2155 ],
2156 'copyuploaddisabled' => [
2157 'code' => 'copyuploaddisabled',
2158 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.'
2159 ],
2160 'copyuploadbaddomain' => [
2161 'code' => 'copyuploadbaddomain',
2162 'info' => 'Uploads by URL are not allowed from this domain.'
2163 ],
2164 'copyuploadbadurl' => [
2165 'code' => 'copyuploadbadurl',
2166 'info' => 'Upload not allowed from this URL.'
2167 ],
2168
2169 'filename-tooshort' => [
2170 'code' => 'filename-tooshort',
2171 'info' => 'The filename is too short'
2172 ],
2173 'filename-toolong' => [ 'code' => 'filename-toolong', 'info' => 'The filename is too long' ],
2174 'illegal-filename' => [
2175 'code' => 'illegal-filename',
2176 'info' => 'The filename is not allowed'
2177 ],
2178 'filetype-missing' => [
2179 'code' => 'filetype-missing',
2180 'info' => 'The file is missing an extension'
2181 ],
2182
2183 'mustbeloggedin' => [ 'code' => 'mustbeloggedin', 'info' => 'You must be logged in to $1.' ]
2184 ];
2185 // @codingStandardsIgnoreEnd
2186
2192 public function dieReadOnly() {
2193 $parsed = $this->parseMsg( [ 'readonlytext' ] );
2194 $this->dieUsage( $parsed['info'], $parsed['code'], /* http error */ 0,
2195 [ 'readonlyreason' => wfReadOnlyReason() ] );
2196 }
2197
2203 public function dieUsageMsg( $error ) {
2204 # most of the time we send a 1 element, so we might as well send it as
2205 # a string and make this an array here.
2206 if ( is_string( $error ) ) {
2207 $error = [ $error ];
2208 }
2209 $parsed = $this->parseMsg( $error );
2210 $extraData = isset( $parsed['data'] ) ? $parsed['data'] : null;
2211 $this->dieUsage( $parsed['info'], $parsed['code'], 0, $extraData );
2212 }
2213
2221 public function dieUsageMsgOrDebug( $error ) {
2222 if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
2223 $this->dieUsageMsg( $error );
2224 }
2225
2226 if ( is_string( $error ) ) {
2227 $error = [ $error ];
2228 }
2229 $parsed = $this->parseMsg( $error );
2230 $this->setWarning( '$wgDebugAPI: ' . $parsed['code'] . ' - ' . $parsed['info'] );
2231 }
2232
2240 protected function dieContinueUsageIf( $condition ) {
2241 if ( $condition ) {
2242 $this->dieUsage(
2243 'Invalid continue param. You should pass the original value returned by the previous query',
2244 'badcontinue' );
2245 }
2246 }
2247
2253 public function parseMsg( $error ) {
2254 // Check whether someone passed the whole array, instead of one element as
2255 // documented. This breaks if it's actually an array of fallback keys, but
2256 // that's long-standing misbehavior introduced in r87627 to incorrectly
2257 // fix T30797.
2258 if ( is_array( $error ) ) {
2259 $first = reset( $error );
2260 if ( is_array( $first ) ) {
2261 wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
2262 $error = $first;
2263 }
2264 }
2265
2266 $msg = Message::newFromSpecifier( $error );
2267
2268 if ( $msg instanceof IApiMessage ) {
2269 return [
2270 'code' => $msg->getApiCode(),
2271 'info' => $msg->inLanguage( 'en' )->useDatabase( false )->text(),
2272 'data' => $msg->getApiData()
2273 ];
2274 }
2275
2276 $key = $msg->getKey();
2277 if ( isset( self::$messageMap[$key] ) ) {
2278 $params = $msg->getParams();
2279 return [
2280 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $params ),
2281 'info' => wfMsgReplaceArgs( self::$messageMap[$key]['info'], $params )
2282 ];
2283 }
2284
2285 // If the key isn't present, throw an "unknown error"
2286 return $this->parseMsg( [ 'unknownerror', $key ] );
2287 }
2288
2295 protected static function dieDebug( $method, $message ) {
2296 throw new MWException( "Internal error in $method: $message" );
2297 }
2298
2304 public function logFeatureUsage( $feature ) {
2305 $request = $this->getRequest();
2306 $s = '"' . addslashes( $feature ) . '"' .
2307 ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
2308 ' "' . $request->getIP() . '"' .
2309 ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
2310 ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
2311 wfDebugLog( 'api-feature-usage', $s, 'private' );
2312 }
2313
2316 /************************************************************************/
2326 protected function getDescriptionMessage() {
2327 return "apihelp-{$this->getModulePath()}-description";
2328 }
2329
2337 public function getFinalDescription() {
2338 $desc = $this->getDescription();
2339 Hooks::run( 'APIGetDescription', [ &$this, &$desc ] );
2340 $desc = self::escapeWikiText( $desc );
2341 if ( is_array( $desc ) ) {
2342 $desc = implode( "\n", $desc );
2343 } else {
2344 $desc = (string)$desc;
2345 }
2346
2347 $msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2348 $this->getModulePrefix(),
2349 $this->getModuleName(),
2350 $this->getModulePath(),
2351 ] );
2352 if ( !$msg->exists() ) {
2353 $msg = $this->msg( 'api-help-fallback-description', $desc );
2354 }
2355 $msgs = [ $msg ];
2356
2357 Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2358
2359 return $msgs;
2360 }
2361
2370 public function getFinalParams( $flags = 0 ) {
2371 $params = $this->getAllowedParams( $flags );
2372 if ( !$params ) {
2373 $params = [];
2374 }
2375
2376 if ( $this->needsToken() ) {
2377 $params['token'] = [
2378 ApiBase::PARAM_TYPE => 'string',
2382 'api-help-param-token',
2383 $this->needsToken(),
2384 ],
2385 ] + ( isset( $params['token'] ) ? $params['token'] : [] );
2386 }
2387
2388 Hooks::run( 'APIGetAllowedParams', [ &$this, &$params, $flags ] );
2389
2390 return $params;
2391 }
2392
2400 public function getFinalParamDescription() {
2401 $prefix = $this->getModulePrefix();
2402 $name = $this->getModuleName();
2403 $path = $this->getModulePath();
2404
2405 $desc = $this->getParamDescription();
2406 Hooks::run( 'APIGetParamDescription', [ &$this, &$desc ] );
2407
2408 if ( !$desc ) {
2409 $desc = [];
2410 }
2411 $desc = self::escapeWikiText( $desc );
2412
2414 $msgs = [];
2415 foreach ( $params as $param => $settings ) {
2416 if ( !is_array( $settings ) ) {
2417 $settings = [];
2418 }
2419
2420 $d = isset( $desc[$param] ) ? $desc[$param] : '';
2421 if ( is_array( $d ) ) {
2422 // Special handling for prop parameters
2423 $d = array_map( function ( $line ) {
2424 if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
2425 $line = "\n;{$m[1]}:{$m[2]}";
2426 }
2427 return $line;
2428 }, $d );
2429 $d = implode( ' ', $d );
2430 }
2431
2432 if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
2433 $msg = $settings[ApiBase::PARAM_HELP_MSG];
2434 } else {
2435 $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2436 if ( !$msg->exists() ) {
2437 $msg = $this->msg( 'api-help-fallback-parameter', $d );
2438 }
2439 }
2440 $msg = ApiBase::makeMessage( $msg, $this->getContext(),
2441 [ $prefix, $param, $name, $path ] );
2442 if ( !$msg ) {
2443 self::dieDebug( __METHOD__,
2444 'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2445 }
2446 $msgs[$param] = [ $msg ];
2447
2448 if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2449 if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2450 self::dieDebug( __METHOD__,
2451 'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2452 }
2453 if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
2454 self::dieDebug( __METHOD__,
2455 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2456 'ApiBase::PARAM_TYPE is an array' );
2457 }
2458
2459 $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
2460 foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
2461 if ( isset( $valueMsgs[$value] ) ) {
2462 $msg = $valueMsgs[$value];
2463 } else {
2464 $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2465 }
2466 $m = ApiBase::makeMessage( $msg, $this->getContext(),
2467 [ $prefix, $param, $name, $path, $value ] );
2468 if ( $m ) {
2469 $m = new ApiHelpParamValueMessage(
2470 $value,
2471 [ $m->getKey(), 'api-help-param-no-description' ],
2472 $m->getParams()
2473 );
2474 $msgs[$param][] = $m->setContext( $this->getContext() );
2475 } else {
2476 self::dieDebug( __METHOD__,
2477 "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2478 }
2479 }
2480 }
2481
2482 if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2483 if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2484 self::dieDebug( __METHOD__,
2485 'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2486 }
2487 foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
2488 $m = ApiBase::makeMessage( $m, $this->getContext(),
2489 [ $prefix, $param, $name, $path ] );
2490 if ( $m ) {
2491 $msgs[$param][] = $m;
2492 } else {
2493 self::dieDebug( __METHOD__,
2494 'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2495 }
2496 }
2497 }
2498 }
2499
2500 Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2501
2502 return $msgs;
2503 }
2504
2514 protected function getHelpFlags() {
2515 $flags = [];
2516
2517 if ( $this->isDeprecated() ) {
2518 $flags[] = 'deprecated';
2519 }
2520 if ( $this->isInternal() ) {
2521 $flags[] = 'internal';
2522 }
2523 if ( $this->isReadMode() ) {
2524 $flags[] = 'readrights';
2525 }
2526 if ( $this->isWriteMode() ) {
2527 $flags[] = 'writerights';
2528 }
2529 if ( $this->mustBePosted() ) {
2530 $flags[] = 'mustbeposted';
2531 }
2532
2533 return $flags;
2534 }
2535
2547 protected function getModuleSourceInfo() {
2548 global $IP;
2549
2550 if ( $this->mModuleSource !== false ) {
2551 return $this->mModuleSource;
2552 }
2553
2554 // First, try to find where the module comes from...
2555 $rClass = new ReflectionClass( $this );
2556 $path = $rClass->getFileName();
2557 if ( !$path ) {
2558 // No path known?
2559 $this->mModuleSource = null;
2560 return null;
2561 }
2562 $path = realpath( $path ) ?: $path;
2563
2564 // Build map of extension directories to extension info
2565 if ( self::$extensionInfo === null ) {
2566 $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2567 self::$extensionInfo = [
2568 realpath( __DIR__ ) ?: __DIR__ => [
2569 'path' => $IP,
2570 'name' => 'MediaWiki',
2571 'license-name' => 'GPL-2.0+',
2572 ],
2573 realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2574 realpath( $extDir ) ?: $extDir => null,
2575 ];
2576 $keep = [
2577 'path' => null,
2578 'name' => null,
2579 'namemsg' => null,
2580 'license-name' => null,
2581 ];
2582 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2583 foreach ( $group as $ext ) {
2584 if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2585 // This shouldn't happen, but does anyway.
2586 continue;
2587 }
2588
2589 $extpath = $ext['path'];
2590 if ( !is_dir( $extpath ) ) {
2591 $extpath = dirname( $extpath );
2592 }
2593 self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2594 array_intersect_key( $ext, $keep );
2595 }
2596 }
2597 foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2598 $extpath = $ext['path'];
2599 if ( !is_dir( $extpath ) ) {
2600 $extpath = dirname( $extpath );
2601 }
2602 self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2603 array_intersect_key( $ext, $keep );
2604 }
2605 }
2606
2607 // Now traverse parent directories until we find a match or run out of
2608 // parents.
2609 do {
2610 if ( array_key_exists( $path, self::$extensionInfo ) ) {
2611 // Found it!
2612 $this->mModuleSource = self::$extensionInfo[$path];
2613 return $this->mModuleSource;
2614 }
2615
2616 $oldpath = $path;
2617 $path = dirname( $path );
2618 } while ( $path !== $oldpath );
2619
2620 // No idea what extension this might be.
2621 $this->mModuleSource = null;
2622 return null;
2623 }
2624
2636 public function modifyHelp( array &$help, array $options, array &$tocData ) {
2637 }
2638
2641 /************************************************************************/
2655 protected function getDescription() {
2656 return false;
2657 }
2658
2671 protected function getParamDescription() {
2672 return [];
2673 }
2674
2691 protected function getExamples() {
2692 return false;
2693 }
2694
2700 public function getModuleProfileName( $db = false ) {
2701 wfDeprecated( __METHOD__, '1.25' );
2702 return '';
2703 }
2704
2708 public function profileIn() {
2709 // No wfDeprecated() yet because extensions call this and might need to
2710 // keep doing so for BC.
2711 }
2712
2716 public function profileOut() {
2717 // No wfDeprecated() yet because extensions call this and might need to
2718 // keep doing so for BC.
2719 }
2720
2724 public function safeProfileOut() {
2725 wfDeprecated( __METHOD__, '1.25' );
2726 }
2727
2732 public function getProfileTime() {
2733 wfDeprecated( __METHOD__, '1.25' );
2734 return 0;
2735 }
2736
2740 public function profileDBIn() {
2741 wfDeprecated( __METHOD__, '1.25' );
2742 }
2743
2747 public function profileDBOut() {
2748 wfDeprecated( __METHOD__, '1.25' );
2749 }
2750
2755 public function getProfileDBTime() {
2756 wfDeprecated( __METHOD__, '1.25' );
2757 return 0;
2758 }
2759
2764 protected function useTransactionalTimeLimit() {
2765 if ( $this->getRequest()->wasPosted() ) {
2767 }
2768 }
2769
2771}
2772
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfUrlencode( $s)
We want some things to be included as literal characters in our title URLs for prettiness,...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
wfGetAllCallers( $limit=3)
Return a string consisting of callers in the stack.
wfTransactionalTimeLimit()
Set PHP's time limit to the larger of php.ini or $wgTransactionalTimeLimit.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$IP
Definition WebStart.php:58
$line
Definition cdb.php:59
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:39
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:472
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition ApiBase.php:112
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right,...
Definition ApiBase.php:97
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is 'submodule', map parameter values to submodule paths.
Definition ApiBase.php:165
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition ApiBase.php:106
safeProfileOut()
Definition ApiBase.php:2724
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:709
getModuleFromPath( $path)
Get a module from its module path.
Definition ApiBase.php:546
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:612
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:672
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set.
Definition ApiBase.php:825
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition ApiBase.php:1522
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition ApiBase.php:91
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition ApiBase.php:1465
getParamDescription()
Returns an array of parameter descriptions.
Definition ApiBase.php:2671
isDeprecated()
Indicates whether this module is deprecated.
Definition ApiBase.php:388
dieContinueUsageIf( $condition)
Die with the $prefix.
Definition ApiBase.php:2240
array null bool $mModuleSource
Definition ApiBase.php:209
getParent()
Get the parent of this module.
Definition ApiBase.php:498
validateUser( $value, $encParamName)
Validate and normalize of parameters of type 'user'.
Definition ApiBase.php:1409
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:2295
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiBase.php:329
getErrorFromStatus( $status, &$extraData=null)
Get error (as code, string) from a Status object.
Definition ApiBase.php:1630
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition ApiBase.php:254
getMain()
Get the main module.
Definition ApiBase.php:480
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:88
getCustomPrinter()
If the module may only be used with a certain format module, it should override this method to return...
Definition ApiBase.php:267
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition ApiBase.php:1434
const PARAM_SENSITIVE
(boolean) Is the parameter sensitive? Note 'password'-type fields are always sensitive regardless of ...
Definition ApiBase.php:179
warnOrDie( $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition ApiBase.php:1565
parseMsg( $error)
Return the error message related to a certain array.
Definition ApiBase.php:2253
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition ApiBase.php:142
getErrorFormatter()
Get the error formatter.
Definition ApiBase.php:598
isWriteMode()
Indicates whether this module requires write mode.
Definition ApiBase.php:371
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:50
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition ApiBase.php:398
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:793
getDescription()
Returns the description string for this module.
Definition ApiBase.php:2655
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:132
dieReadOnly()
Helper function for readonly errors.
Definition ApiBase.php:2192
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition ApiBase.php:103
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition ApiBase.php:2636
const PARAM_VALUE_LINKS
(string[]) When PARAM_TYPE is an array, this may be an array mapping those values to page titles whic...
Definition ApiBase.php:149
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition ApiBase.php:216
setContinuationManager( $manager)
Set the continuation manager.
Definition ApiBase.php:638
getProfileTime()
Definition ApiBase.php:2732
getExamplesMessages()
Returns usage examples for this module.
Definition ApiBase.php:282
extractRequestParams( $parseLimit=true)
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:685
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
dieUsageMsg( $error)
Output the error message related to a certain array.
Definition ApiBase.php:2203
setWarning( $warning)
Set warning section for this module.
Definition ApiBase.php:1554
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBase.php:345
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition ApiBase.php:2547
const PARAM_SUBMODULE_PARAM_PREFIX
(string) When PARAM_TYPE is 'submodule', used to indicate the 'g' prefix added by ApiQueryGeneratorBa...
Definition ApiBase.php:172
static escapeWikiText( $v)
A subset of wfEscapeWikiText for BC texts.
Definition ApiBase.php:1498
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition ApiBase.php:100
profileDBOut()
Definition ApiBase.php:2747
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiBase.php:379
getWebUITokenSalt(array $params)
Fetch the salt used in the Web UI corresponding to this module.
Definition ApiBase.php:433
static array $extensionInfo
Maps extension paths to info arrays.
Definition ApiBase.php:200
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:184
isMain()
Returns true if this module is the main module ($this === $this->mMainModule), false otherwise.
Definition ApiBase.php:489
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition ApiBase.php:190
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition ApiBase.php:1191
ApiMain $mMainModule
Definition ApiBase.php:203
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition ApiBase.php:1296
getFinalDescription()
Get final module description, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2337
string $mModuleName
Definition ApiBase.php:205
static truncateArray(&$arr, $limit)
Truncate an array to a certain length.
Definition ApiBase.php:1449
string $mModulePrefix
Definition ApiBase.php:205
profileOut()
Definition ApiBase.php:2716
getResult()
Get the result object.
Definition ApiBase.php:584
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:747
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiBase.php:420
getDescriptionMessage()
Return the description message.
Definition ApiBase.php:2326
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition ApiBase.php:919
getModulePath()
Get the path to this module.
Definition ApiBase.php:528
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE 'integer', enforce PARAM_MIN and PARAM_MAX?
Definition ApiBase.php:118
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition ApiBase.php:2304
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition ApiBase.php:355
getHelpFlags()
Generates the list of flags for the help screen and for action=paraminfo.
Definition ApiBase.php:2514
const LIMIT_SML1
Slow query, standard limit.
Definition ApiBase.php:188
dieUsageMsgOrDebug( $error)
Will only set a warning instead of failing if the global $wgDebugAPI is set to true.
Definition ApiBase.php:2221
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2370
getWatchlistValue( $watchlist, $titleObj, $userOption=null)
Return true if we're to watch the page, false if not, null if no change.
Definition ApiBase.php:877
static $messageMap
Array that maps message keys to error messages.
Definition ApiBase.php:1684
isReadMode()
Indicates whether this module requires read rights.
Definition ApiBase.php:363
getExamples()
Returns usage examples for this module.
Definition ApiBase.php:2691
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:125
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition ApiBase.php:770
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition ApiBase.php:197
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:186
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:464
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition ApiBase.php:840
validateTimestamp( $value, $encParamName)
Validate and normalize of parameters of type 'timestamp'.
Definition ApiBase.php:1335
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition ApiBase.php:2764
dieStatus( $status)
Throw a UsageException based on the errors in the Status object.
Definition ApiBase.php:1674
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2400
getProfileDBTime()
Definition ApiBase.php:2755
dieBlocked(Block $block)
Throw a UsageException, which will (if uncaught) call the main module's error handler and die with an...
Definition ApiBase.php:1602
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition ApiBase.php:662
getModuleProfileName( $db=false)
Definition ApiBase.php:2700
getContinuationManager()
Get the continuation manager.
Definition ApiBase.php:624
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition ApiBase.php:1207
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
dieUsage( $description, $errorCode, $httpRespCode=0, $extradata=null)
Throw a UsageException, which will (if uncaught) call the main module's error handler and die with an...
Definition ApiBase.php:1585
validateToken( $token, array $params)
Validate the supplied token.
Definition ApiBase.php:1374
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition ApiBase.php:449
profileDBIn()
Definition ApiBase.php:2740
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:721
profileIn()
Definition ApiBase.php:2708
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:53
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:512
parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues)
Return an array of values that were given in a 'a|b|c' notation, after it optionally validates them a...
Definition ApiBase.php:1231
Message subclass that prepends wikitext for API help.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:43
static getTokenTypeSalts()
Get the salts for known token types.
static getToken(User $user, MediaWiki\Session\Session $session, $salt)
Get a token from a salt.
static getBlockInfo(Block $block)
Get basic info about a given block.
Extension of RawMessage implementing IApiMessage.
getType()
Get the type of target for this particular block.
Definition Block.php:1360
const TYPE_AUTO
Definition Block.php:81
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...
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
getUser()
Get the User object.
getRequest()
Get the WebRequest object.
getConfig()
Get the Config object.
msg()
Get a Message object with context set Parameters are the same as wfMessage()
IContextSource $context
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Set the IContextSource object.
MediaWiki exception.
The Message class provides methods which fulfil two basic services:
Definition Message.php:159
This exception will be thrown when dieUsage is called to stop module execution.
Definition ApiMain.php:1860
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition WikiPage.php:153
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:115
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
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
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
const NS_USER
Definition Defines.php:58
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition hooks.txt:1049
the array() calling protocol came about after MediaWiki 1.4rc1.
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:249
namespace are movable Hooks may change this value to override the return value of MWNamespace::isMovable(). 'NewDifferenceEngine' do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk my contributions etc etc otherwise the built in rate limiting checks are if enabled allows for interception of redirect as a string mapping parameter names to values & $type
Definition hooks.txt:2568
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:183
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:986
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition hooks.txt:1096
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 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;div ...>$1&lt;/div>"). - flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException':Called before an exception(or PHP error) is logged. This is meant for integration with external error aggregation services
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition hooks.txt:2710
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2685
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers if desired 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 inclusive $limit
Definition hooks.txt:1135
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:1949
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
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:887
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:37
Interface for messages with machine-readable data for use by the API.
Interface for objects which can provide a MediaWiki context on request.
$help
Definition mcc.php:32
const DB_REPLICA
Definition defines.php:22
$params
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
Definition defines.php:6
const TS_MW
MediaWiki concatenated string timestamp (YYYYMMDDHHMMSS)
Definition defines.php:11