MediaWiki REL1_29
ApiBase.php
Go to the documentation of this file.
1<?php
28
41abstract class ApiBase extends ContextSource {
42
52 const PARAM_DFLT = 0;
53
55 const PARAM_ISMULTI = 1;
56
91 const PARAM_TYPE = 2;
92
94 const PARAM_MAX = 3;
95
100 const PARAM_MAX2 = 4;
101
103 const PARAM_MIN = 5;
104
107
110
115 const PARAM_REQUIRED = 8;
116
122
128 const PARAM_HELP_MSG = 10;
129
136
146
153
161
169
176
183 const PARAM_ALL = 17;
184
190
191 /*
192 * (boolean) Is the parameter sensitive? Note 'password'-type fields are
193 * always sensitive regardless of the value of this field.
194 * @since 1.29
195 */
196 const PARAM_SENSITIVE = 19;
197
201
203 const LIMIT_BIG1 = 500;
205 const LIMIT_BIG2 = 5000;
207 const LIMIT_SML1 = 50;
209 const LIMIT_SML2 = 500;
210
217
219 private static $extensionInfo = null;
220
225 private $mSlaveDB = null;
226 private $mParamCache = [];
228 private $mModuleSource = false;
229
235 public function __construct( ApiMain $mainModule, $moduleName, $modulePrefix = '' ) {
236 $this->mMainModule = $mainModule;
237 $this->mModuleName = $moduleName;
238 $this->mModulePrefix = $modulePrefix;
239
240 if ( !$this->isMain() ) {
241 $this->setContext( $mainModule->getContext() );
242 }
243 }
244
245 /************************************************************************/
266 abstract public function execute();
267
273 public function getModuleManager() {
274 return null;
275 }
276
286 public function getCustomPrinter() {
287 return null;
288 }
289
301 protected function getExamplesMessages() {
302 // Fall back to old non-localised method
303 $ret = [];
304
305 $examples = $this->getExamples();
306 if ( $examples ) {
307 if ( !is_array( $examples ) ) {
308 $examples = [ $examples ];
309 } elseif ( $examples && ( count( $examples ) & 1 ) == 0 &&
310 array_keys( $examples ) === range( 0, count( $examples ) - 1 ) &&
311 !preg_match( '/^\s*api\.php\?/', $examples[0] )
312 ) {
313 // Fix up the ugly "even numbered elements are description, odd
314 // numbered elemts are the link" format (see doc for self::getExamples)
315 $tmp = [];
316 $examplesCount = count( $examples );
317 for ( $i = 0; $i < $examplesCount; $i += 2 ) {
318 $tmp[$examples[$i + 1]] = $examples[$i];
319 }
320 $examples = $tmp;
321 }
322
323 foreach ( $examples as $k => $v ) {
324 if ( is_numeric( $k ) ) {
325 $qs = $v;
326 $msg = '';
327 } else {
328 $qs = $k;
329 $msg = self::escapeWikiText( $v );
330 if ( is_array( $msg ) ) {
331 $msg = implode( ' ', $msg );
332 }
333 }
334
335 $qs = preg_replace( '/^\s*api\.php\?/', '', $qs );
336 $ret[$qs] = $this->msg( 'api-help-fallback-example', [ $msg ] );
337 }
338 }
339
340 return $ret;
341 }
342
348 public function getHelpUrls() {
349 return [];
350 }
351
364 protected function getAllowedParams( /* $flags = 0 */ ) {
365 // int $flags is not declared because it causes "Strict standards"
366 // warning. Most derived classes do not implement it.
367 return [];
368 }
369
374 public function shouldCheckMaxlag() {
375 return true;
376 }
377
382 public function isReadMode() {
383 return true;
384 }
385
397 public function isWriteMode() {
398 return false;
399 }
400
405 public function mustBePosted() {
406 return $this->needsToken() !== false;
407 }
408
414 public function isDeprecated() {
415 return false;
416 }
417
424 public function isInternal() {
425 return false;
426 }
427
446 public function needsToken() {
447 return false;
448 }
449
459 protected function getWebUITokenSalt( array $params ) {
460 return null;
461 }
462
475 public function getConditionalRequestData( $condition ) {
476 return null;
477 }
478
481 /************************************************************************/
490 public function getModuleName() {
491 return $this->mModuleName;
492 }
493
498 public function getModulePrefix() {
500 }
501
506 public function getMain() {
507 return $this->mMainModule;
508 }
509
515 public function isMain() {
516 return $this === $this->mMainModule;
517 }
518
524 public function getParent() {
525 return $this->isMain() ? null : $this->getMain();
526 }
527
538 public function lacksSameOriginSecurity() {
539 // Main module has this method overridden
540 // Safety - avoid infinite loop:
541 if ( $this->isMain() ) {
542 ApiBase::dieDebug( __METHOD__, 'base method was called on main module.' );
543 }
544
545 return $this->getMain()->lacksSameOriginSecurity();
546 }
547
554 public function getModulePath() {
555 if ( $this->isMain() ) {
556 return 'main';
557 } elseif ( $this->getParent()->isMain() ) {
558 return $this->getModuleName();
559 } else {
560 return $this->getParent()->getModulePath() . '+' . $this->getModuleName();
561 }
562 }
563
572 public function getModuleFromPath( $path ) {
573 $module = $this->getMain();
574 if ( $path === 'main' ) {
575 return $module;
576 }
577
578 $parts = explode( '+', $path );
579 if ( count( $parts ) === 1 ) {
580 // In case the '+' was typed into URL, it resolves as a space
581 $parts = explode( ' ', $path );
582 }
583
584 $count = count( $parts );
585 for ( $i = 0; $i < $count; $i++ ) {
586 $parent = $module;
587 $manager = $parent->getModuleManager();
588 if ( $manager === null ) {
589 $errorPath = implode( '+', array_slice( $parts, 0, $i ) );
590 $this->dieWithError( [ 'apierror-badmodule-nosubmodules', $errorPath ], 'badmodule' );
591 }
592 $module = $manager->getModule( $parts[$i] );
593
594 if ( $module === null ) {
595 $errorPath = $i ? implode( '+', array_slice( $parts, 0, $i ) ) : $parent->getModuleName();
596 $this->dieWithError(
597 [ 'apierror-badmodule-badsubmodule', $errorPath, wfEscapeWikiText( $parts[$i] ) ],
598 'badmodule'
599 );
600 }
601 }
602
603 return $module;
604 }
605
610 public function getResult() {
611 // Main module has getResult() method overridden
612 // Safety - avoid infinite loop:
613 if ( $this->isMain() ) {
614 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
615 }
616
617 return $this->getMain()->getResult();
618 }
619
624 public function getErrorFormatter() {
625 // Main module has getErrorFormatter() 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()->getErrorFormatter();
632 }
633
638 protected function getDB() {
639 if ( !isset( $this->mSlaveDB ) ) {
640 $this->mSlaveDB = wfGetDB( DB_REPLICA, 'api' );
641 }
642
643 return $this->mSlaveDB;
644 }
645
650 public function getContinuationManager() {
651 // Main module has getContinuationManager() method overridden
652 // Safety - avoid infinite loop:
653 if ( $this->isMain() ) {
654 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
655 }
656
657 return $this->getMain()->getContinuationManager();
658 }
659
664 public function setContinuationManager( $manager ) {
665 // Main module has setContinuationManager() method overridden
666 // Safety - avoid infinite loop:
667 if ( $this->isMain() ) {
668 ApiBase::dieDebug( __METHOD__, 'base method was called on main module. ' );
669 }
670
671 $this->getMain()->setContinuationManager( $manager );
672 }
673
676 /************************************************************************/
689 return null;
690 }
691
699 public function encodeParamName( $paramName ) {
700 if ( is_array( $paramName ) ) {
701 return array_map( function ( $name ) {
702 return $this->mModulePrefix . $name;
703 }, $paramName );
704 } else {
705 return $this->mModulePrefix . $paramName;
706 }
707 }
708
718 public function extractRequestParams( $parseLimit = true ) {
719 // Cache parameters, for performance and to avoid T26564.
720 if ( !isset( $this->mParamCache[$parseLimit] ) ) {
721 $params = $this->getFinalParams();
722 $results = [];
723
724 if ( $params ) { // getFinalParams() can return false
725 foreach ( $params as $paramName => $paramSettings ) {
726 $results[$paramName] = $this->getParameterFromSettings(
727 $paramName, $paramSettings, $parseLimit );
728 }
729 }
730 $this->mParamCache[$parseLimit] = $results;
731 }
732
733 return $this->mParamCache[$parseLimit];
734 }
735
742 protected function getParameter( $paramName, $parseLimit = true ) {
743 $paramSettings = $this->getFinalParams()[$paramName];
744
745 return $this->getParameterFromSettings( $paramName, $paramSettings, $parseLimit );
746 }
747
754 public function requireOnlyOneParameter( $params, $required /*...*/ ) {
755 $required = func_get_args();
756 array_shift( $required );
757
758 $intersection = array_intersect( array_keys( array_filter( $params,
759 [ $this, 'parameterNotEmpty' ] ) ), $required );
760
761 if ( count( $intersection ) > 1 ) {
762 $this->dieWithError( [
763 'apierror-invalidparammix',
764 Message::listParam( array_map(
765 function ( $p ) {
766 return '<var>' . $this->encodeParamName( $p ) . '</var>';
767 },
768 array_values( $intersection )
769 ) ),
770 count( $intersection ),
771 ] );
772 } elseif ( count( $intersection ) == 0 ) {
773 $this->dieWithError( [
774 'apierror-missingparam-one-of',
775 Message::listParam( array_map(
776 function ( $p ) {
777 return '<var>' . $this->encodeParamName( $p ) . '</var>';
778 },
779 array_values( $required )
780 ) ),
781 count( $required ),
782 ], 'missingparam' );
783 }
784 }
785
792 public function requireMaxOneParameter( $params, $required /*...*/ ) {
793 $required = func_get_args();
794 array_shift( $required );
795
796 $intersection = array_intersect( array_keys( array_filter( $params,
797 [ $this, 'parameterNotEmpty' ] ) ), $required );
798
799 if ( count( $intersection ) > 1 ) {
800 $this->dieWithError( [
801 'apierror-invalidparammix',
802 Message::listParam( array_map(
803 function ( $p ) {
804 return '<var>' . $this->encodeParamName( $p ) . '</var>';
805 },
806 array_values( $intersection )
807 ) ),
808 count( $intersection ),
809 ] );
810 }
811 }
812
820 public function requireAtLeastOneParameter( $params, $required /*...*/ ) {
821 $required = func_get_args();
822 array_shift( $required );
823
824 $intersection = array_intersect(
825 array_keys( array_filter( $params, [ $this, 'parameterNotEmpty' ] ) ),
826 $required
827 );
828
829 if ( count( $intersection ) == 0 ) {
830 $this->dieWithError( [
831 'apierror-missingparam-at-least-one-of',
832 Message::listParam( array_map(
833 function ( $p ) {
834 return '<var>' . $this->encodeParamName( $p ) . '</var>';
835 },
836 array_values( $required )
837 ) ),
838 count( $required ),
839 ], 'missingparam' );
840 }
841 }
842
850 public function requirePostedParameters( $params, $prefix = 'prefix' ) {
851 // Skip if $wgDebugAPI is set or we're in internal mode
852 if ( $this->getConfig()->get( 'DebugAPI' ) || $this->getMain()->isInternalMode() ) {
853 return;
854 }
855
856 $queryValues = $this->getRequest()->getQueryValues();
857 $badParams = [];
858 foreach ( $params as $param ) {
859 if ( $prefix !== 'noprefix' ) {
860 $param = $this->encodeParamName( $param );
861 }
862 if ( array_key_exists( $param, $queryValues ) ) {
863 $badParams[] = $param;
864 }
865 }
866
867 if ( $badParams ) {
868 $this->dieWithError(
869 [ 'apierror-mustpostparams', join( ', ', $badParams ), count( $badParams ) ]
870 );
871 }
872 }
873
880 private function parameterNotEmpty( $x ) {
881 return !is_null( $x ) && $x !== false;
882 }
883
895 public function getTitleOrPageId( $params, $load = false ) {
896 $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
897
898 $pageObj = null;
899 if ( isset( $params['title'] ) ) {
900 $titleObj = Title::newFromText( $params['title'] );
901 if ( !$titleObj || $titleObj->isExternal() ) {
902 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
903 }
904 if ( !$titleObj->canExist() ) {
905 $this->dieWithError( 'apierror-pagecannotexist' );
906 }
907 $pageObj = WikiPage::factory( $titleObj );
908 if ( $load !== false ) {
909 $pageObj->loadPageData( $load );
910 }
911 } elseif ( isset( $params['pageid'] ) ) {
912 if ( $load === false ) {
913 $load = 'fromdb';
914 }
915 $pageObj = WikiPage::newFromID( $params['pageid'], $load );
916 if ( !$pageObj ) {
917 $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
918 }
919 }
920
921 return $pageObj;
922 }
923
933 $this->requireOnlyOneParameter( $params, 'title', 'pageid' );
934
935 $titleObj = null;
936 if ( isset( $params['title'] ) ) {
937 $titleObj = Title::newFromText( $params['title'] );
938 if ( !$titleObj || $titleObj->isExternal() ) {
939 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $params['title'] ) ] );
940 }
941 return $titleObj;
942 } elseif ( isset( $params['pageid'] ) ) {
943 $titleObj = Title::newFromID( $params['pageid'] );
944 if ( !$titleObj ) {
945 $this->dieWithError( [ 'apierror-nosuchpageid', $params['pageid'] ] );
946 }
947 }
948
949 return $titleObj;
950 }
951
960 protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) {
961
962 $userWatching = $this->getUser()->isWatched( $titleObj, User::IGNORE_USER_RIGHTS );
963
964 switch ( $watchlist ) {
965 case 'watch':
966 return true;
967
968 case 'unwatch':
969 return false;
970
971 case 'preferences':
972 # If the user is already watching, don't bother checking
973 if ( $userWatching ) {
974 return true;
975 }
976 # If no user option was passed, use watchdefault and watchcreations
977 if ( is_null( $userOption ) ) {
978 return $this->getUser()->getBoolOption( 'watchdefault' ) ||
979 $this->getUser()->getBoolOption( 'watchcreations' ) && !$titleObj->exists();
980 }
981
982 # Watch the article based on the user preference
983 return $this->getUser()->getBoolOption( $userOption );
984
985 case 'nochange':
986 return $userWatching;
987
988 default:
989 return $userWatching;
990 }
991 }
992
1002 protected function getParameterFromSettings( $paramName, $paramSettings, $parseLimit ) {
1003 // Some classes may decide to change parameter names
1004 $encParamName = $this->encodeParamName( $paramName );
1005
1006 // Shorthand
1007 if ( !is_array( $paramSettings ) ) {
1008 $paramSettings = [
1009 self::PARAM_DFLT => $paramSettings,
1010 ];
1011 }
1012
1013 $default = isset( $paramSettings[self::PARAM_DFLT] )
1014 ? $paramSettings[self::PARAM_DFLT]
1015 : null;
1016 $multi = isset( $paramSettings[self::PARAM_ISMULTI] )
1017 ? $paramSettings[self::PARAM_ISMULTI]
1018 : false;
1019 $type = isset( $paramSettings[self::PARAM_TYPE] )
1020 ? $paramSettings[self::PARAM_TYPE]
1021 : null;
1022 $dupes = isset( $paramSettings[self::PARAM_ALLOW_DUPLICATES] )
1023 ? $paramSettings[self::PARAM_ALLOW_DUPLICATES]
1024 : false;
1025 $deprecated = isset( $paramSettings[self::PARAM_DEPRECATED] )
1026 ? $paramSettings[self::PARAM_DEPRECATED]
1027 : false;
1028 $required = isset( $paramSettings[self::PARAM_REQUIRED] )
1029 ? $paramSettings[self::PARAM_REQUIRED]
1030 : false;
1031 $allowAll = isset( $paramSettings[self::PARAM_ALL] )
1032 ? $paramSettings[self::PARAM_ALL]
1033 : false;
1034
1035 // When type is not given, and no choices, the type is the same as $default
1036 if ( !isset( $type ) ) {
1037 if ( isset( $default ) ) {
1038 $type = gettype( $default );
1039 } else {
1040 $type = 'NULL'; // allow everything
1041 }
1042 }
1043
1044 if ( $type == 'password' || !empty( $paramSettings[self::PARAM_SENSITIVE] ) ) {
1045 $this->getMain()->markParamsSensitive( $encParamName );
1046 }
1047
1048 if ( $type == 'boolean' ) {
1049 if ( isset( $default ) && $default !== false ) {
1050 // Having a default value of anything other than 'false' is not allowed
1052 __METHOD__,
1053 "Boolean param $encParamName's default is set to '$default'. " .
1054 'Boolean parameters must default to false.'
1055 );
1056 }
1057
1058 $value = $this->getMain()->getCheck( $encParamName );
1059 } elseif ( $type == 'upload' ) {
1060 if ( isset( $default ) ) {
1061 // Having a default value is not allowed
1063 __METHOD__,
1064 "File upload param $encParamName's default is set to " .
1065 "'$default'. File upload parameters may not have a default." );
1066 }
1067 if ( $multi ) {
1068 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1069 }
1070 $value = $this->getMain()->getUpload( $encParamName );
1071 if ( !$value->exists() ) {
1072 // This will get the value without trying to normalize it
1073 // (because trying to normalize a large binary file
1074 // accidentally uploaded as a field fails spectacularly)
1075 $value = $this->getMain()->getRequest()->unsetVal( $encParamName );
1076 if ( $value !== null ) {
1077 $this->dieWithError(
1078 [ 'apierror-badupload', $encParamName ],
1079 "badupload_{$encParamName}"
1080 );
1081 }
1082 }
1083 } else {
1084 $value = $this->getMain()->getVal( $encParamName, $default );
1085
1086 if ( isset( $value ) && $type == 'namespace' ) {
1087 $type = MWNamespace::getValidNamespaces();
1088 if ( isset( $paramSettings[self::PARAM_EXTRA_NAMESPACES] ) &&
1089 is_array( $paramSettings[self::PARAM_EXTRA_NAMESPACES] )
1090 ) {
1091 $type = array_merge( $type, $paramSettings[self::PARAM_EXTRA_NAMESPACES] );
1092 }
1093 // By default, namespace parameters allow ALL_DEFAULT_STRING to be used to specify
1094 // all namespaces.
1095 $allowAll = true;
1096 }
1097 if ( isset( $value ) && $type == 'submodule' ) {
1098 if ( isset( $paramSettings[self::PARAM_SUBMODULE_MAP] ) ) {
1099 $type = array_keys( $paramSettings[self::PARAM_SUBMODULE_MAP] );
1100 } else {
1101 $type = $this->getModuleManager()->getNames( $paramName );
1102 }
1103 }
1104
1105 $request = $this->getMain()->getRequest();
1106 $rawValue = $request->getRawVal( $encParamName );
1107 if ( $rawValue === null ) {
1108 $rawValue = $default;
1109 }
1110
1111 // Preserve U+001F for self::parseMultiValue(), or error out if that won't be called
1112 if ( isset( $value ) && substr( $rawValue, 0, 1 ) === "\x1f" ) {
1113 if ( $multi ) {
1114 // This loses the potential $wgContLang->checkTitleEncoding() transformation
1115 // done by WebRequest for $_GET. Let's call that a feature.
1116 $value = join( "\x1f", $request->normalizeUnicode( explode( "\x1f", $rawValue ) ) );
1117 } else {
1118 $this->dieWithError( 'apierror-badvalue-notmultivalue', 'badvalue_notmultivalue' );
1119 }
1120 }
1121
1122 // Check for NFC normalization, and warn
1123 if ( $rawValue !== $value ) {
1124 $this->handleParamNormalization( $paramName, $value, $rawValue );
1125 }
1126 }
1127
1128 $allSpecifier = ( is_string( $allowAll ) ? $allowAll : self::ALL_DEFAULT_STRING );
1129 if ( $allowAll && $multi && is_array( $type ) && in_array( $allSpecifier, $type, true ) ) {
1131 __METHOD__,
1132 "For param $encParamName, PARAM_ALL collides with a possible value" );
1133 }
1134 if ( isset( $value ) && ( $multi || is_array( $type ) ) ) {
1135 $value = $this->parseMultiValue(
1136 $encParamName,
1137 $value,
1138 $multi,
1139 is_array( $type ) ? $type : null,
1140 $allowAll ? $allSpecifier : null
1141 );
1142 }
1143
1144 // More validation only when choices were not given
1145 // choices were validated in parseMultiValue()
1146 if ( isset( $value ) ) {
1147 if ( !is_array( $type ) ) {
1148 switch ( $type ) {
1149 case 'NULL': // nothing to do
1150 break;
1151 case 'string':
1152 case 'text':
1153 case 'password':
1154 if ( $required && $value === '' ) {
1155 $this->dieWithError( [ 'apierror-missingparam', $paramName ] );
1156 }
1157 break;
1158 case 'integer': // Force everything using intval() and optionally validate limits
1159 $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null;
1160 $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null;
1161 $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] )
1162 ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false;
1163
1164 if ( is_array( $value ) ) {
1165 $value = array_map( 'intval', $value );
1166 if ( !is_null( $min ) || !is_null( $max ) ) {
1167 foreach ( $value as &$v ) {
1168 $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits );
1169 }
1170 }
1171 } else {
1172 $value = intval( $value );
1173 if ( !is_null( $min ) || !is_null( $max ) ) {
1174 $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits );
1175 }
1176 }
1177 break;
1178 case 'limit':
1179 if ( !$parseLimit ) {
1180 // Don't do any validation whatsoever
1181 break;
1182 }
1183 if ( !isset( $paramSettings[self::PARAM_MAX] )
1184 || !isset( $paramSettings[self::PARAM_MAX2] )
1185 ) {
1187 __METHOD__,
1188 "MAX1 or MAX2 are not defined for the limit $encParamName"
1189 );
1190 }
1191 if ( $multi ) {
1192 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1193 }
1194 $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : 0;
1195 if ( $value == 'max' ) {
1196 $value = $this->getMain()->canApiHighLimits()
1197 ? $paramSettings[self::PARAM_MAX2]
1198 : $paramSettings[self::PARAM_MAX];
1199 $this->getResult()->addParsedLimit( $this->getModuleName(), $value );
1200 } else {
1201 $value = intval( $value );
1202 $this->validateLimit(
1203 $paramName,
1204 $value,
1205 $min,
1206 $paramSettings[self::PARAM_MAX],
1207 $paramSettings[self::PARAM_MAX2]
1208 );
1209 }
1210 break;
1211 case 'boolean':
1212 if ( $multi ) {
1213 ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" );
1214 }
1215 break;
1216 case 'timestamp':
1217 if ( is_array( $value ) ) {
1218 foreach ( $value as $key => $val ) {
1219 $value[$key] = $this->validateTimestamp( $val, $encParamName );
1220 }
1221 } else {
1222 $value = $this->validateTimestamp( $value, $encParamName );
1223 }
1224 break;
1225 case 'user':
1226 if ( is_array( $value ) ) {
1227 foreach ( $value as $key => $val ) {
1228 $value[$key] = $this->validateUser( $val, $encParamName );
1229 }
1230 } else {
1231 $value = $this->validateUser( $value, $encParamName );
1232 }
1233 break;
1234 case 'upload': // nothing to do
1235 break;
1236 case 'tags':
1237 // If change tagging was requested, check that the tags are valid.
1238 if ( !is_array( $value ) && !$multi ) {
1239 $value = [ $value ];
1240 }
1242 if ( !$tagsStatus->isGood() ) {
1243 $this->dieStatus( $tagsStatus );
1244 }
1245 break;
1246 default:
1247 ApiBase::dieDebug( __METHOD__, "Param $encParamName's type is unknown - $type" );
1248 }
1249 }
1250
1251 // Throw out duplicates if requested
1252 if ( !$dupes && is_array( $value ) ) {
1253 $value = array_unique( $value );
1254 }
1255
1256 // Set a warning if a deprecated parameter has been passed
1257 if ( $deprecated && $value !== false ) {
1258 $feature = $encParamName;
1259 $m = $this;
1260 while ( !$m->isMain() ) {
1261 $p = $m->getParent();
1262 $name = $m->getModuleName();
1263 $param = $p->encodeParamName( $p->getModuleManager()->getModuleGroup( $name ) );
1264 $feature = "{$param}={$name}&{$feature}";
1265 $m = $p;
1266 }
1267 $this->addDeprecation( [ 'apiwarn-deprecation-parameter', $encParamName ], $feature );
1268 }
1269 } elseif ( $required ) {
1270 $this->dieWithError( [ 'apierror-missingparam', $paramName ] );
1271 }
1272
1273 return $value;
1274 }
1275
1283 protected function handleParamNormalization( $paramName, $value, $rawValue ) {
1284 $encParamName = $this->encodeParamName( $paramName );
1285 $this->addWarning( [ 'apiwarn-badutf8', $encParamName ] );
1286 }
1287
1295 protected function explodeMultiValue( $value, $limit ) {
1296 if ( substr( $value, 0, 1 ) === "\x1f" ) {
1297 $sep = "\x1f";
1298 $value = substr( $value, 1 );
1299 } else {
1300 $sep = '|';
1301 }
1302
1303 return explode( $sep, $value, $limit );
1304 }
1305
1321 protected function parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues,
1322 $allSpecifier = null
1323 ) {
1324 if ( ( trim( $value ) === '' || trim( $value ) === "\x1f" ) && $allowMultiple ) {
1325 return [];
1326 }
1327
1328 // This is a bit awkward, but we want to avoid calling canApiHighLimits()
1329 // because it unstubs $wgUser
1330 $valuesList = $this->explodeMultiValue( $value, self::LIMIT_SML2 + 1 );
1331 $sizeLimit = count( $valuesList ) > self::LIMIT_SML1 && $this->mMainModule->canApiHighLimits()
1334
1335 if ( $allowMultiple && is_array( $allowedValues ) && $allSpecifier &&
1336 count( $valuesList ) === 1 && $valuesList[0] === $allSpecifier
1337 ) {
1338 return $allowedValues;
1339 }
1340
1341 if ( self::truncateArray( $valuesList, $sizeLimit ) ) {
1342 $this->addDeprecation(
1343 [ 'apiwarn-toomanyvalues', $valueName, $sizeLimit ],
1344 "too-many-$valueName-for-{$this->getModulePath()}"
1345 );
1346 }
1347
1348 if ( !$allowMultiple && count( $valuesList ) != 1 ) {
1349 // T35482 - Allow entries with | in them for non-multiple values
1350 if ( in_array( $value, $allowedValues, true ) ) {
1351 return $value;
1352 }
1353
1354 if ( is_array( $allowedValues ) ) {
1355 $values = array_map( function ( $v ) {
1356 return '<kbd>' . wfEscapeWikiText( $v ) . '</kbd>';
1357 }, $allowedValues );
1358 $this->dieWithError( [
1359 'apierror-multival-only-one-of',
1360 $valueName,
1361 Message::listParam( $values ),
1362 count( $values ),
1363 ], "multival_$valueName" );
1364 } else {
1365 $this->dieWithError( [
1366 'apierror-multival-only-one',
1367 $valueName,
1368 ], "multival_$valueName" );
1369 }
1370 }
1371
1372 if ( is_array( $allowedValues ) ) {
1373 // Check for unknown values
1374 $unknown = array_map( 'wfEscapeWikiText', array_diff( $valuesList, $allowedValues ) );
1375 if ( count( $unknown ) ) {
1376 if ( $allowMultiple ) {
1377 $this->addWarning( [
1378 'apiwarn-unrecognizedvalues',
1379 $valueName,
1380 Message::listParam( $unknown, 'comma' ),
1381 count( $unknown ),
1382 ] );
1383 } else {
1384 $this->dieWithError(
1385 [ 'apierror-unrecognizedvalue', $valueName, wfEscapeWikiText( $valuesList[0] ) ],
1386 "unknown_$valueName"
1387 );
1388 }
1389 }
1390 // Now throw them out
1391 $valuesList = array_intersect( $valuesList, $allowedValues );
1392 }
1393
1394 return $allowMultiple ? $valuesList : $valuesList[0];
1395 }
1396
1407 protected function validateLimit( $paramName, &$value, $min, $max, $botMax = null,
1408 $enforceLimits = false
1409 ) {
1410 if ( !is_null( $min ) && $value < $min ) {
1411 $msg = ApiMessage::create(
1412 [ 'apierror-integeroutofrange-belowminimum',
1413 $this->encodeParamName( $paramName ), $min, $value ],
1414 'integeroutofrange',
1415 [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1416 );
1417 $this->warnOrDie( $msg, $enforceLimits );
1418 $value = $min;
1419 }
1420
1421 // Minimum is always validated, whereas maximum is checked only if not
1422 // running in internal call mode
1423 if ( $this->getMain()->isInternalMode() ) {
1424 return;
1425 }
1426
1427 // Optimization: do not check user's bot status unless really needed -- skips db query
1428 // assumes $botMax >= $max
1429 if ( !is_null( $max ) && $value > $max ) {
1430 if ( !is_null( $botMax ) && $this->getMain()->canApiHighLimits() ) {
1431 if ( $value > $botMax ) {
1432 $msg = ApiMessage::create(
1433 [ 'apierror-integeroutofrange-abovebotmax',
1434 $this->encodeParamName( $paramName ), $botMax, $value ],
1435 'integeroutofrange',
1436 [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1437 );
1438 $this->warnOrDie( $msg, $enforceLimits );
1439 $value = $botMax;
1440 }
1441 } else {
1442 $msg = ApiMessage::create(
1443 [ 'apierror-integeroutofrange-abovemax',
1444 $this->encodeParamName( $paramName ), $max, $value ],
1445 'integeroutofrange',
1446 [ 'min' => $min, 'max' => $max, 'botMax' => $botMax ?: $max ]
1447 );
1448 $this->warnOrDie( $msg, $enforceLimits );
1449 $value = $max;
1450 }
1451 }
1452 }
1453
1460 protected function validateTimestamp( $value, $encParamName ) {
1461 // Confusing synonyms for the current time accepted by wfTimestamp()
1462 // (wfTimestamp() also accepts various non-strings and the string of 14
1463 // ASCII NUL bytes, but those can't get here)
1464 if ( !$value ) {
1465 $this->addDeprecation(
1466 [ 'apiwarn-unclearnowtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1467 'unclear-"now"-timestamp'
1468 );
1469 return wfTimestamp( TS_MW );
1470 }
1471
1472 // Explicit synonym for the current time
1473 if ( $value === 'now' ) {
1474 return wfTimestamp( TS_MW );
1475 }
1476
1477 $unixTimestamp = wfTimestamp( TS_UNIX, $value );
1478 if ( $unixTimestamp === false ) {
1479 $this->dieWithError(
1480 [ 'apierror-badtimestamp', $encParamName, wfEscapeWikiText( $value ) ],
1481 "badtimestamp_{$encParamName}"
1482 );
1483 }
1484
1485 return wfTimestamp( TS_MW, $unixTimestamp );
1486 }
1487
1497 final public function validateToken( $token, array $params ) {
1498 $tokenType = $this->needsToken();
1500 if ( !isset( $salts[$tokenType] ) ) {
1501 throw new MWException(
1502 "Module '{$this->getModuleName()}' tried to use token type '$tokenType' " .
1503 'without registering it'
1504 );
1505 }
1506
1507 $tokenObj = ApiQueryTokens::getToken(
1508 $this->getUser(), $this->getRequest()->getSession(), $salts[$tokenType]
1509 );
1510 if ( $tokenObj->match( $token ) ) {
1511 return true;
1512 }
1513
1514 $webUiSalt = $this->getWebUITokenSalt( $params );
1515 if ( $webUiSalt !== null && $this->getUser()->matchEditToken(
1516 $token,
1517 $webUiSalt,
1518 $this->getRequest()
1519 ) ) {
1520 return true;
1521 }
1522
1523 return false;
1524 }
1525
1532 private function validateUser( $value, $encParamName ) {
1533 $title = Title::makeTitleSafe( NS_USER, $value );
1534 if ( $title === null || $title->hasFragment() ) {
1535 $this->dieWithError(
1536 [ 'apierror-baduser', $encParamName, wfEscapeWikiText( $value ) ],
1537 "baduser_{$encParamName}"
1538 );
1539 }
1540
1541 return $title->getText();
1542 }
1543
1546 /************************************************************************/
1557 protected function setWatch( $watch, $titleObj, $userOption = null ) {
1558 $value = $this->getWatchlistValue( $watch, $titleObj, $userOption );
1559 if ( $value === null ) {
1560 return;
1561 }
1562
1563 WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() );
1564 }
1565
1572 public static function truncateArray( &$arr, $limit ) {
1573 $modified = false;
1574 while ( count( $arr ) > $limit ) {
1575 array_pop( $arr );
1576 $modified = true;
1577 }
1578
1579 return $modified;
1580 }
1581
1588 public function getWatchlistUser( $params ) {
1589 if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) {
1590 $user = User::newFromName( $params['owner'], false );
1591 if ( !( $user && $user->getId() ) ) {
1592 $this->dieWithError(
1593 [ 'nosuchusershort', wfEscapeWikiText( $params['owner'] ) ], 'bad_wlowner'
1594 );
1595 }
1596 $token = $user->getOption( 'watchlisttoken' );
1597 if ( $token == '' || !hash_equals( $token, $params['token'] ) ) {
1598 $this->dieWithError( 'apierror-bad-watchlist-token', 'bad_wltoken' );
1599 }
1600 } else {
1601 if ( !$this->getUser()->isLoggedIn() ) {
1602 $this->dieWithError( 'watchlistanontext', 'notloggedin' );
1603 }
1604 $this->checkUserRightsAny( 'viewmywatchlist' );
1605 $user = $this->getUser();
1606 }
1607
1608 return $user;
1609 }
1610
1618 private static function escapeWikiText( $v ) {
1619 if ( is_array( $v ) ) {
1620 return array_map( 'self::escapeWikiText', $v );
1621 } else {
1622 return strtr( $v, [
1623 '__' => '_&#95;', '{' => '&#123;', '}' => '&#125;',
1624 '[[Category:' => '[[:Category:',
1625 '[[File:' => '[[:File:', '[[Image:' => '[[:Image:',
1626 ] );
1627 }
1628 }
1629
1642 public static function makeMessage( $msg, IContextSource $context, array $params = null ) {
1643 if ( is_string( $msg ) ) {
1644 $msg = wfMessage( $msg );
1645 } elseif ( is_array( $msg ) ) {
1646 $msg = call_user_func_array( 'wfMessage', $msg );
1647 }
1648 if ( !$msg instanceof Message ) {
1649 return null;
1650 }
1651
1652 $msg->setContext( $context );
1653 if ( $params ) {
1654 $msg->params( $params );
1655 }
1656
1657 return $msg;
1658 }
1659
1667 public function errorArrayToStatus( array $errors, User $user = null ) {
1668 if ( $user === null ) {
1669 $user = $this->getUser();
1670 }
1671
1672 $status = Status::newGood();
1673 foreach ( $errors as $error ) {
1674 if ( is_array( $error ) && $error[0] === 'blockedtext' && $user->getBlock() ) {
1676 'apierror-blocked',
1677 'blocked',
1678 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1679 ) );
1680 } elseif ( is_array( $error ) && $error[0] === 'autoblockedtext' && $user->getBlock() ) {
1682 'apierror-autoblocked',
1683 'autoblocked',
1684 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1685 ) );
1686 } elseif ( is_array( $error ) && $error[0] === 'systemblockedtext' && $user->getBlock() ) {
1688 'apierror-systemblocked',
1689 'blocked',
1690 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $user->getBlock() ) ]
1691 ) );
1692 } else {
1693 call_user_func_array( [ $status, 'fatal' ], (array)$error );
1694 }
1695 }
1696 return $status;
1697 }
1698
1701 /************************************************************************/
1720 public function addWarning( $msg, $code = null, $data = null ) {
1721 $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg, $code, $data );
1722 }
1723
1734 public function addDeprecation( $msg, $feature, $data = [] ) {
1735 $data = (array)$data;
1736 if ( $feature !== null ) {
1737 $data['feature'] = $feature;
1738 $this->logFeatureUsage( $feature );
1739 }
1740 $this->addWarning( $msg, 'deprecation', $data );
1741
1742 // No real need to deduplicate here, ApiErrorFormatter does that for
1743 // us (assuming the hook is deterministic).
1744 $msgs = [ $this->msg( 'api-usage-mailinglist-ref' ) ];
1745 Hooks::run( 'ApiDeprecationHelp', [ &$msgs ] );
1746 if ( count( $msgs ) > 1 ) {
1747 $key = '$' . join( ' $', range( 1, count( $msgs ) ) );
1748 $msg = ( new RawMessage( $key ) )->params( $msgs );
1749 } else {
1750 $msg = reset( $msgs );
1751 }
1752 $this->getMain()->addWarning( $msg, 'deprecation-help' );
1753 }
1754
1767 public function addError( $msg, $code = null, $data = null ) {
1768 $this->getErrorFormatter()->addError( $this->getModulePath(), $msg, $code, $data );
1769 }
1770
1779 public function addMessagesFromStatus( StatusValue $status, $types = [ 'warning', 'error' ] ) {
1780 $this->getErrorFormatter()->addMessagesFromStatus( $this->getModulePath(), $status, $types );
1781 }
1782
1796 public function dieWithError( $msg, $code = null, $data = null, $httpCode = null ) {
1797 throw ApiUsageException::newWithMessage( $this, $msg, $code, $data, $httpCode );
1798 }
1799
1808 public function dieWithException( $exception, array $options = [] ) {
1809 $this->dieWithError(
1810 $this->getErrorFormatter()->getMessageFromException( $exception, $options )
1811 );
1812 }
1813
1820 private function warnOrDie( ApiMessage $msg, $enforceLimits = false ) {
1821 if ( $enforceLimits ) {
1822 $this->dieWithError( $msg );
1823 } else {
1824 $this->addWarning( $msg );
1825 }
1826 }
1827
1836 public function dieBlocked( Block $block ) {
1837 // Die using the appropriate message depending on block type
1838 if ( $block->getType() == Block::TYPE_AUTO ) {
1839 $this->dieWithError(
1840 'apierror-autoblocked',
1841 'autoblocked',
1842 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1843 );
1844 } else {
1845 $this->dieWithError(
1846 'apierror-blocked',
1847 'blocked',
1848 [ 'blockinfo' => ApiQueryUserInfo::getBlockInfo( $block ) ]
1849 );
1850 }
1851 }
1852
1861 public function dieStatus( StatusValue $status ) {
1862 if ( $status->isGood() ) {
1863 throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
1864 }
1865
1866 throw new ApiUsageException( $this, $status );
1867 }
1868
1874 public function dieReadOnly() {
1875 $this->dieWithError(
1876 'apierror-readonly',
1877 'readonly',
1878 [ 'readonlyreason' => wfReadOnlyReason() ]
1879 );
1880 }
1881
1890 public function checkUserRightsAny( $rights, $user = null ) {
1891 if ( !$user ) {
1892 $user = $this->getUser();
1893 }
1894 $rights = (array)$rights;
1895 if ( !call_user_func_array( [ $user, 'isAllowedAny' ], $rights ) ) {
1896 $this->dieWithError( [ 'apierror-permissiondenied', $this->msg( "action-{$rights[0]}" ) ] );
1897 }
1898 }
1899
1908 public function checkTitleUserPermissions( Title $title, $actions, $user = null ) {
1909 if ( !$user ) {
1910 $user = $this->getUser();
1911 }
1912
1913 $errors = [];
1914 foreach ( (array)$actions as $action ) {
1915 $errors = array_merge( $errors, $title->getUserPermissionsErrors( $action, $user ) );
1916 }
1917 if ( $errors ) {
1918 $this->dieStatus( $this->errorArrayToStatus( $errors, $user ) );
1919 }
1920 }
1921
1933 public function dieWithErrorOrDebug( $msg, $code = null, $data = null, $httpCode = null ) {
1934 if ( $this->getConfig()->get( 'DebugAPI' ) !== true ) {
1935 $this->dieWithError( $msg, $code, $data, $httpCode );
1936 } else {
1937 $this->addWarning( $msg, $code, $data );
1938 }
1939 }
1940
1950 protected function dieContinueUsageIf( $condition ) {
1951 if ( $condition ) {
1952 $this->dieWithError( 'apierror-badcontinue' );
1953 }
1954 }
1955
1962 protected static function dieDebug( $method, $message ) {
1963 throw new MWException( "Internal error in $method: $message" );
1964 }
1965
1972 public function logFeatureUsage( $feature ) {
1973 $request = $this->getRequest();
1974 $s = '"' . addslashes( $feature ) . '"' .
1975 ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
1976 ' "' . $request->getIP() . '"' .
1977 ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
1978 ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
1979 wfDebugLog( 'api-feature-usage', $s, 'private' );
1980 }
1981
1984 /************************************************************************/
1994 protected function getDescriptionMessage() {
1995 return "apihelp-{$this->getModulePath()}-description";
1996 }
1997
2005 public function getFinalDescription() {
2006 $desc = $this->getDescription();
2007
2008 // Avoid PHP 7.1 warning of passing $this by reference
2009 $apiModule = $this;
2010 Hooks::run( 'APIGetDescription', [ &$apiModule, &$desc ] );
2011 $desc = self::escapeWikiText( $desc );
2012 if ( is_array( $desc ) ) {
2013 $desc = implode( "\n", $desc );
2014 } else {
2015 $desc = (string)$desc;
2016 }
2017
2018 $msg = ApiBase::makeMessage( $this->getDescriptionMessage(), $this->getContext(), [
2019 $this->getModulePrefix(),
2020 $this->getModuleName(),
2021 $this->getModulePath(),
2022 ] );
2023 if ( !$msg->exists() ) {
2024 $msg = $this->msg( 'api-help-fallback-description', $desc );
2025 }
2026 $msgs = [ $msg ];
2027
2028 Hooks::run( 'APIGetDescriptionMessages', [ $this, &$msgs ] );
2029
2030 return $msgs;
2031 }
2032
2041 public function getFinalParams( $flags = 0 ) {
2042 $params = $this->getAllowedParams( $flags );
2043 if ( !$params ) {
2044 $params = [];
2045 }
2046
2047 if ( $this->needsToken() ) {
2048 $params['token'] = [
2049 ApiBase::PARAM_TYPE => 'string',
2053 'api-help-param-token',
2054 $this->needsToken(),
2055 ],
2056 ] + ( isset( $params['token'] ) ? $params['token'] : [] );
2057 }
2058
2059 // Avoid PHP 7.1 warning of passing $this by reference
2060 $apiModule = $this;
2061 Hooks::run( 'APIGetAllowedParams', [ &$apiModule, &$params, $flags ] );
2062
2063 return $params;
2064 }
2065
2073 public function getFinalParamDescription() {
2074 $prefix = $this->getModulePrefix();
2075 $name = $this->getModuleName();
2076 $path = $this->getModulePath();
2077
2078 $desc = $this->getParamDescription();
2079
2080 // Avoid PHP 7.1 warning of passing $this by reference
2081 $apiModule = $this;
2082 Hooks::run( 'APIGetParamDescription', [ &$apiModule, &$desc ] );
2083
2084 if ( !$desc ) {
2085 $desc = [];
2086 }
2087 $desc = self::escapeWikiText( $desc );
2088
2090 $msgs = [];
2091 foreach ( $params as $param => $settings ) {
2092 if ( !is_array( $settings ) ) {
2093 $settings = [];
2094 }
2095
2096 $d = isset( $desc[$param] ) ? $desc[$param] : '';
2097 if ( is_array( $d ) ) {
2098 // Special handling for prop parameters
2099 $d = array_map( function ( $line ) {
2100 if ( preg_match( '/^\s+(\S+)\s+-\s+(.+)$/', $line, $m ) ) {
2101 $line = "\n;{$m[1]}:{$m[2]}";
2102 }
2103 return $line;
2104 }, $d );
2105 $d = implode( ' ', $d );
2106 }
2107
2108 if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
2109 $msg = $settings[ApiBase::PARAM_HELP_MSG];
2110 } else {
2111 $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
2112 if ( !$msg->exists() ) {
2113 $msg = $this->msg( 'api-help-fallback-parameter', $d );
2114 }
2115 }
2116 $msg = ApiBase::makeMessage( $msg, $this->getContext(),
2117 [ $prefix, $param, $name, $path ] );
2118 if ( !$msg ) {
2119 self::dieDebug( __METHOD__,
2120 'Value in ApiBase::PARAM_HELP_MSG is not valid' );
2121 }
2122 $msgs[$param] = [ $msg ];
2123
2124 if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2125 if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
2126 self::dieDebug( __METHOD__,
2127 'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
2128 }
2129 if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
2130 self::dieDebug( __METHOD__,
2131 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
2132 'ApiBase::PARAM_TYPE is an array' );
2133 }
2134
2135 $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
2136 foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
2137 if ( isset( $valueMsgs[$value] ) ) {
2138 $msg = $valueMsgs[$value];
2139 } else {
2140 $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
2141 }
2142 $m = ApiBase::makeMessage( $msg, $this->getContext(),
2143 [ $prefix, $param, $name, $path, $value ] );
2144 if ( $m ) {
2145 $m = new ApiHelpParamValueMessage(
2146 $value,
2147 [ $m->getKey(), 'api-help-param-no-description' ],
2148 $m->getParams()
2149 );
2150 $msgs[$param][] = $m->setContext( $this->getContext() );
2151 } else {
2152 self::dieDebug( __METHOD__,
2153 "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
2154 }
2155 }
2156 }
2157
2158 if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2159 if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
2160 self::dieDebug( __METHOD__,
2161 'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
2162 }
2163 foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
2164 $m = ApiBase::makeMessage( $m, $this->getContext(),
2165 [ $prefix, $param, $name, $path ] );
2166 if ( $m ) {
2167 $msgs[$param][] = $m;
2168 } else {
2169 self::dieDebug( __METHOD__,
2170 'Value in ApiBase::PARAM_HELP_MSG_APPEND is not valid' );
2171 }
2172 }
2173 }
2174 }
2175
2176 Hooks::run( 'APIGetParamDescriptionMessages', [ $this, &$msgs ] );
2177
2178 return $msgs;
2179 }
2180
2190 protected function getHelpFlags() {
2191 $flags = [];
2192
2193 if ( $this->isDeprecated() ) {
2194 $flags[] = 'deprecated';
2195 }
2196 if ( $this->isInternal() ) {
2197 $flags[] = 'internal';
2198 }
2199 if ( $this->isReadMode() ) {
2200 $flags[] = 'readrights';
2201 }
2202 if ( $this->isWriteMode() ) {
2203 $flags[] = 'writerights';
2204 }
2205 if ( $this->mustBePosted() ) {
2206 $flags[] = 'mustbeposted';
2207 }
2208
2209 return $flags;
2210 }
2211
2223 protected function getModuleSourceInfo() {
2224 global $IP;
2225
2226 if ( $this->mModuleSource !== false ) {
2227 return $this->mModuleSource;
2228 }
2229
2230 // First, try to find where the module comes from...
2231 $rClass = new ReflectionClass( $this );
2232 $path = $rClass->getFileName();
2233 if ( !$path ) {
2234 // No path known?
2235 $this->mModuleSource = null;
2236 return null;
2237 }
2238 $path = realpath( $path ) ?: $path;
2239
2240 // Build map of extension directories to extension info
2241 if ( self::$extensionInfo === null ) {
2242 $extDir = $this->getConfig()->get( 'ExtensionDirectory' );
2243 self::$extensionInfo = [
2244 realpath( __DIR__ ) ?: __DIR__ => [
2245 'path' => $IP,
2246 'name' => 'MediaWiki',
2247 'license-name' => 'GPL-2.0+',
2248 ],
2249 realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
2250 realpath( $extDir ) ?: $extDir => null,
2251 ];
2252 $keep = [
2253 'path' => null,
2254 'name' => null,
2255 'namemsg' => null,
2256 'license-name' => null,
2257 ];
2258 foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
2259 foreach ( $group as $ext ) {
2260 if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
2261 // This shouldn't happen, but does anyway.
2262 continue;
2263 }
2264
2265 $extpath = $ext['path'];
2266 if ( !is_dir( $extpath ) ) {
2267 $extpath = dirname( $extpath );
2268 }
2269 self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2270 array_intersect_key( $ext, $keep );
2271 }
2272 }
2273 foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
2274 $extpath = $ext['path'];
2275 if ( !is_dir( $extpath ) ) {
2276 $extpath = dirname( $extpath );
2277 }
2278 self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
2279 array_intersect_key( $ext, $keep );
2280 }
2281 }
2282
2283 // Now traverse parent directories until we find a match or run out of
2284 // parents.
2285 do {
2286 if ( array_key_exists( $path, self::$extensionInfo ) ) {
2287 // Found it!
2288 $this->mModuleSource = self::$extensionInfo[$path];
2289 return $this->mModuleSource;
2290 }
2291
2292 $oldpath = $path;
2293 $path = dirname( $path );
2294 } while ( $path !== $oldpath );
2295
2296 // No idea what extension this might be.
2297 $this->mModuleSource = null;
2298 return null;
2299 }
2300
2312 public function modifyHelp( array &$help, array $options, array &$tocData ) {
2313 }
2314
2317 /************************************************************************/
2331 protected function getDescription() {
2332 return false;
2333 }
2334
2347 protected function getParamDescription() {
2348 return [];
2349 }
2350
2367 protected function getExamples() {
2368 return false;
2369 }
2370
2376 public function getModuleProfileName( $db = false ) {
2377 wfDeprecated( __METHOD__, '1.25' );
2378 return '';
2379 }
2380
2384 public function profileIn() {
2385 // No wfDeprecated() yet because extensions call this and might need to
2386 // keep doing so for BC.
2387 }
2388
2392 public function profileOut() {
2393 // No wfDeprecated() yet because extensions call this and might need to
2394 // keep doing so for BC.
2395 }
2396
2400 public function safeProfileOut() {
2401 wfDeprecated( __METHOD__, '1.25' );
2402 }
2403
2408 public function getProfileTime() {
2409 wfDeprecated( __METHOD__, '1.25' );
2410 return 0;
2411 }
2412
2416 public function profileDBIn() {
2417 wfDeprecated( __METHOD__, '1.25' );
2418 }
2419
2423 public function profileDBOut() {
2424 wfDeprecated( __METHOD__, '1.25' );
2425 }
2426
2431 public function getProfileDBTime() {
2432 wfDeprecated( __METHOD__, '1.25' );
2433 return 0;
2434 }
2435
2440 protected function useTransactionalTimeLimit() {
2441 if ( $this->getRequest()->wasPosted() ) {
2443 }
2444 }
2445
2450 public function setWarning( $warning ) {
2451 $msg = new ApiRawMessage( $warning, 'warning' );
2452 $this->getErrorFormatter()->addWarning( $this->getModulePath(), $msg );
2453 }
2454
2468 public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
2469 $this->dieWithError(
2470 new RawMessage( '$1', [ $description ] ),
2471 $errorCode,
2472 $extradata,
2473 $httpRespCode
2474 );
2475 }
2476
2487 public function getErrorFromStatus( $status, &$extraData = null ) {
2488 if ( $status->isGood() ) {
2489 throw new MWException( 'Successful status passed to ApiBase::dieStatus' );
2490 }
2491
2492 $errors = $status->getErrorsByType( 'error' );
2493 if ( !$errors ) {
2494 // No errors? Assume the warnings should be treated as errors
2495 $errors = $status->getErrorsByType( 'warning' );
2496 }
2497 if ( !$errors ) {
2498 // Still no errors? Punt
2499 $errors = [ [ 'message' => 'unknownerror-nocode', 'params' => [] ] ];
2500 }
2501
2502 if ( $errors[0]['message'] instanceof MessageSpecifier ) {
2503 $msg = $errors[0]['message'];
2504 } else {
2505 $msg = new Message( $errors[0]['message'], $errors[0]['params'] );
2506 }
2507 if ( !$msg instanceof IApiMessage ) {
2508 $key = $msg->getKey();
2509 $params = $msg->getParams();
2510 array_unshift( $params, isset( self::$messageMap[$key] ) ? self::$messageMap[$key] : $key );
2511 $msg = ApiMessage::create( $params );
2512 }
2513
2514 return [
2515 $msg->getApiCode(),
2516 ApiErrorFormatter::stripMarkup( $msg->inLanguage( 'en' )->useDatabase( false )->text() )
2517 ];
2518 }
2519
2528 private static $messageMap = [
2529 'unknownerror' => 'apierror-unknownerror',
2530 'unknownerror-nocode' => 'apierror-unknownerror-nocode',
2531 'ns-specialprotected' => 'ns-specialprotected',
2532 'protectedinterface' => 'protectedinterface',
2533 'namespaceprotected' => 'namespaceprotected',
2534 'customcssprotected' => 'customcssprotected',
2535 'customjsprotected' => 'customjsprotected',
2536 'cascadeprotected' => 'cascadeprotected',
2537 'protectedpagetext' => 'protectedpagetext',
2538 'protect-cantedit' => 'protect-cantedit',
2539 'deleteprotected' => 'deleteprotected',
2540 'badaccess-group0' => 'badaccess-group0',
2541 'badaccess-groups' => 'badaccess-groups',
2542 'titleprotected' => 'titleprotected',
2543 'nocreate-loggedin' => 'nocreate-loggedin',
2544 'nocreatetext' => 'nocreatetext',
2545 'movenologintext' => 'movenologintext',
2546 'movenotallowed' => 'movenotallowed',
2547 'confirmedittext' => 'confirmedittext',
2548 'blockedtext' => 'apierror-blocked',
2549 'autoblockedtext' => 'apierror-autoblocked',
2550 'systemblockedtext' => 'apierror-systemblocked',
2551 'actionthrottledtext' => 'apierror-ratelimited',
2552 'alreadyrolled' => 'alreadyrolled',
2553 'cantrollback' => 'cantrollback',
2554 'readonlytext' => 'readonlytext',
2555 'sessionfailure' => 'sessionfailure',
2556 'cannotdelete' => 'cannotdelete',
2557 'notanarticle' => 'apierror-missingtitle',
2558 'selfmove' => 'selfmove',
2559 'immobile_namespace' => 'apierror-immobilenamespace',
2560 'articleexists' => 'articleexists',
2561 'hookaborted' => 'hookaborted',
2562 'cantmove-titleprotected' => 'cantmove-titleprotected',
2563 'imagenocrossnamespace' => 'imagenocrossnamespace',
2564 'imagetypemismatch' => 'imagetypemismatch',
2565 'ip_range_invalid' => 'ip_range_invalid',
2566 'range_block_disabled' => 'range_block_disabled',
2567 'nosuchusershort' => 'nosuchusershort',
2568 'badipaddress' => 'badipaddress',
2569 'ipb_expiry_invalid' => 'ipb_expiry_invalid',
2570 'ipb_already_blocked' => 'ipb_already_blocked',
2571 'ipb_blocked_as_range' => 'ipb_blocked_as_range',
2572 'ipb_cant_unblock' => 'ipb_cant_unblock',
2573 'mailnologin' => 'apierror-cantsend',
2574 'ipbblocked' => 'ipbblocked',
2575 'ipbnounblockself' => 'ipbnounblockself',
2576 'usermaildisabled' => 'usermaildisabled',
2577 'blockedemailuser' => 'apierror-blockedfrommail',
2578 'notarget' => 'apierror-notarget',
2579 'noemail' => 'noemail',
2580 'rcpatroldisabled' => 'rcpatroldisabled',
2581 'markedaspatrollederror-noautopatrol' => 'markedaspatrollederror-noautopatrol',
2582 'delete-toobig' => 'delete-toobig',
2583 'movenotallowedfile' => 'movenotallowedfile',
2584 'userrights-no-interwiki' => 'userrights-no-interwiki',
2585 'userrights-nodatabase' => 'userrights-nodatabase',
2586 'nouserspecified' => 'nouserspecified',
2587 'noname' => 'noname',
2588 'summaryrequired' => 'apierror-summaryrequired',
2589 'import-rootpage-invalid' => 'import-rootpage-invalid',
2590 'import-rootpage-nosubpage' => 'import-rootpage-nosubpage',
2591 'readrequired' => 'apierror-readapidenied',
2592 'writedisabled' => 'apierror-noapiwrite',
2593 'writerequired' => 'apierror-writeapidenied',
2594 'missingparam' => 'apierror-missingparam',
2595 'invalidtitle' => 'apierror-invalidtitle',
2596 'nosuchpageid' => 'apierror-nosuchpageid',
2597 'nosuchrevid' => 'apierror-nosuchrevid',
2598 'nosuchuser' => 'nosuchusershort',
2599 'invaliduser' => 'apierror-invaliduser',
2600 'invalidexpiry' => 'apierror-invalidexpiry',
2601 'pastexpiry' => 'apierror-pastexpiry',
2602 'create-titleexists' => 'apierror-create-titleexists',
2603 'missingtitle-createonly' => 'apierror-missingtitle-createonly',
2604 'cantblock' => 'apierror-cantblock',
2605 'canthide' => 'apierror-canthide',
2606 'cantblock-email' => 'apierror-cantblock-email',
2607 'cantunblock' => 'apierror-permissiondenied-generic',
2608 'cannotundelete' => 'cannotundelete',
2609 'permdenied-undelete' => 'apierror-permissiondenied-generic',
2610 'createonly-exists' => 'apierror-articleexists',
2611 'nocreate-missing' => 'apierror-missingtitle',
2612 'cantchangecontentmodel' => 'apierror-cantchangecontentmodel',
2613 'nosuchrcid' => 'apierror-nosuchrcid',
2614 'nosuchlogid' => 'apierror-nosuchlogid',
2615 'protect-invalidaction' => 'apierror-protect-invalidaction',
2616 'protect-invalidlevel' => 'apierror-protect-invalidlevel',
2617 'toofewexpiries' => 'apierror-toofewexpiries',
2618 'cantimport' => 'apierror-cantimport',
2619 'cantimport-upload' => 'apierror-cantimport-upload',
2620 'importnofile' => 'importnofile',
2621 'importuploaderrorsize' => 'importuploaderrorsize',
2622 'importuploaderrorpartial' => 'importuploaderrorpartial',
2623 'importuploaderrortemp' => 'importuploaderrortemp',
2624 'importcantopen' => 'importcantopen',
2625 'import-noarticle' => 'import-noarticle',
2626 'importbadinterwiki' => 'importbadinterwiki',
2627 'import-unknownerror' => 'apierror-import-unknownerror',
2628 'cantoverwrite-sharedfile' => 'apierror-cantoverwrite-sharedfile',
2629 'sharedfile-exists' => 'apierror-fileexists-sharedrepo-perm',
2630 'mustbeposted' => 'apierror-mustbeposted',
2631 'show' => 'apierror-show',
2632 'specialpage-cantexecute' => 'apierror-specialpage-cantexecute',
2633 'invalidoldimage' => 'apierror-invalidoldimage',
2634 'nodeleteablefile' => 'apierror-nodeleteablefile',
2635 'fileexists-forbidden' => 'fileexists-forbidden',
2636 'fileexists-shared-forbidden' => 'fileexists-shared-forbidden',
2637 'filerevert-badversion' => 'filerevert-badversion',
2638 'noimageredirect-anon' => 'apierror-noimageredirect-anon',
2639 'noimageredirect-logged' => 'apierror-noimageredirect',
2640 'spamdetected' => 'apierror-spamdetected',
2641 'contenttoobig' => 'apierror-contenttoobig',
2642 'noedit-anon' => 'apierror-noedit-anon',
2643 'noedit' => 'apierror-noedit',
2644 'wasdeleted' => 'apierror-pagedeleted',
2645 'blankpage' => 'apierror-emptypage',
2646 'editconflict' => 'editconflict',
2647 'hashcheckfailed' => 'apierror-badmd5',
2648 'missingtext' => 'apierror-notext',
2649 'emptynewsection' => 'apierror-emptynewsection',
2650 'revwrongpage' => 'apierror-revwrongpage',
2651 'undo-failure' => 'undo-failure',
2652 'content-not-allowed-here' => 'content-not-allowed-here',
2653 'edit-hook-aborted' => 'edit-hook-aborted',
2654 'edit-gone-missing' => 'edit-gone-missing',
2655 'edit-conflict' => 'edit-conflict',
2656 'edit-already-exists' => 'edit-already-exists',
2657 'invalid-file-key' => 'apierror-invalid-file-key',
2658 'nouploadmodule' => 'apierror-nouploadmodule',
2659 'uploaddisabled' => 'uploaddisabled',
2660 'copyuploaddisabled' => 'copyuploaddisabled',
2661 'copyuploadbaddomain' => 'apierror-copyuploadbaddomain',
2662 'copyuploadbadurl' => 'apierror-copyuploadbadurl',
2663 'filename-tooshort' => 'filename-tooshort',
2664 'filename-toolong' => 'filename-toolong',
2665 'illegal-filename' => 'illegal-filename',
2666 'filetype-missing' => 'filetype-missing',
2667 'mustbeloggedin' => 'apierror-mustbeloggedin',
2668 ];
2669
2675 private function parseMsgInternal( $error ) {
2676 $msg = Message::newFromSpecifier( $error );
2677 if ( !$msg instanceof IApiMessage ) {
2678 $key = $msg->getKey();
2679 if ( isset( self::$messageMap[$key] ) ) {
2680 $params = $msg->getParams();
2681 array_unshift( $params, self::$messageMap[$key] );
2682 } else {
2683 $params = [ 'apierror-unknownerror', wfEscapeWikiText( $key ) ];
2684 }
2685 $msg = ApiMessage::create( $params );
2686 }
2687 return $msg;
2688 }
2689
2696 public function parseMsg( $error ) {
2697 // Check whether someone passed the whole array, instead of one element as
2698 // documented. This breaks if it's actually an array of fallback keys, but
2699 // that's long-standing misbehavior introduced in r87627 to incorrectly
2700 // fix T30797.
2701 if ( is_array( $error ) ) {
2702 $first = reset( $error );
2703 if ( is_array( $first ) ) {
2704 wfDebug( __METHOD__ . ' was passed an array of arrays. ' . wfGetAllCallers( 5 ) );
2705 $error = $first;
2706 }
2707 }
2708
2709 $msg = $this->parseMsgInternal( $error );
2710 return [
2711 'code' => $msg->getApiCode(),
2713 $msg->inLanguage( 'en' )->useDatabase( false )->text()
2714 ),
2715 'data' => $msg->getApiData()
2716 ];
2717 }
2718
2725 public function dieUsageMsg( $error ) {
2726 $this->dieWithError( $this->parseMsgInternal( $error ) );
2727 }
2728
2737 public function dieUsageMsgOrDebug( $error ) {
2738 $this->dieWithErrorOrDebug( $this->parseMsgInternal( $error ) );
2739 }
2740
2742}
2743
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
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.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
$IP
Definition WebStart.php:58
$line
Definition cdb.php:58
This abstract class implements many basic API functions, and is the base of all API classes.
Definition ApiBase.php:41
getModulePrefix()
Get parameter prefix (usually two letters or an empty string).
Definition ApiBase.php:498
const PARAM_REQUIRED
(boolean) Is the parameter required?
Definition ApiBase.php:115
const PARAM_MAX2
(integer) Max value allowed for the parameter for users with the apihighlimits right,...
Definition ApiBase.php:100
const PARAM_SUBMODULE_MAP
(string[]) When PARAM_TYPE is 'submodule', map parameter values to submodule paths.
Definition ApiBase.php:168
const PARAM_DEPRECATED
(boolean) Is the parameter deprecated (will show a warning)?
Definition ApiBase.php:109
checkUserRightsAny( $rights, $user=null)
Helper function for permission-denied errors.
Definition ApiBase.php:1890
safeProfileOut()
Definition ApiBase.php:2400
getParameter( $paramName, $parseLimit=true)
Get a value for the given parameter.
Definition ApiBase.php:742
getModuleFromPath( $path)
Get a module from its module path.
Definition ApiBase.php:572
getDB()
Gets a default replica DB connection object.
Definition ApiBase.php:638
encodeParamName( $paramName)
This method mangles parameter name based on the prefix supplied to the constructor.
Definition ApiBase.php:699
parameterNotEmpty( $x)
Callback function used in requireOnlyOneParameter to check whether required parameters are set.
Definition ApiBase.php:880
static makeMessage( $msg, IContextSource $context, array $params=null)
Create a Message from a string or array.
Definition ApiBase.php:1642
const PARAM_MAX
(integer) Max value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition ApiBase.php:94
dieWithError( $msg, $code=null, $data=null, $httpCode=null)
Abort execution with an error.
Definition ApiBase.php:1796
getWatchlistUser( $params)
Gets the user for whom to get the watchlist.
Definition ApiBase.php:1588
getParamDescription()
Returns an array of parameter descriptions.
Definition ApiBase.php:2347
isDeprecated()
Indicates whether this module is deprecated.
Definition ApiBase.php:414
parseMsgInternal( $error)
Definition ApiBase.php:2675
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1950
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:1933
array null bool $mModuleSource
Definition ApiBase.php:228
getParent()
Get the parent of this module.
Definition ApiBase.php:524
validateUser( $value, $encParamName)
Validate and normalize of parameters of type 'user'.
Definition ApiBase.php:1532
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1962
getHelpUrls()
Return links to more detailed help pages about the module.
Definition ApiBase.php:348
getErrorFromStatus( $status, &$extraData=null)
Get error (as code, string) from a Status object.
Definition ApiBase.php:2487
getModuleManager()
Get the module manager, or null if this module has no sub-modules.
Definition ApiBase.php:273
getMain()
Get the main module.
Definition ApiBase.php:506
const PARAM_TYPE
(string|string[]) Either an array of allowed value strings, or a string type as described below.
Definition ApiBase.php:91
getCustomPrinter()
If the module may only be used with a certain format module, it should override this method to return...
Definition ApiBase.php:286
setWatch( $watch, $titleObj, $userOption=null)
Set a watch (or unwatch) based the based on a watchlist parameter.
Definition ApiBase.php:1557
const PARAM_SENSITIVE
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:196
parseMsg( $error)
Return the error message related to a certain array.
Definition ApiBase.php:2696
const PARAM_HELP_MSG_INFO
(array) Specify additional information tags for the parameter.
Definition ApiBase.php:145
getErrorFormatter()
Get the error formatter.
Definition ApiBase.php:624
isWriteMode()
Indicates whether this module requires write mode.
Definition ApiBase.php:397
const PARAM_DFLT
(null|boolean|integer|string) Default value of the parameter.
Definition ApiBase.php:52
isInternal()
Indicates whether this module is "internal" Internal API modules are not (yet) intended for 3rd party...
Definition ApiBase.php:424
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:850
getDescription()
Returns the description string for this module.
Definition ApiBase.php:2331
const PARAM_HELP_MSG_APPEND
((string|array|Message)[]) Specify additional i18n messages to append to the normal message for this ...
Definition ApiBase.php:135
dieReadOnly()
Helper function for readonly errors.
Definition ApiBase.php:1874
const PARAM_ALLOW_DUPLICATES
(boolean) Allow the same value to be set more than once when PARAM_ISMULTI is true?
Definition ApiBase.php:106
modifyHelp(array &$help, array $options, array &$tocData)
Called from ApiHelp before the pieces are joined together and returned.
Definition ApiBase.php:2312
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition ApiBase.php:1667
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:152
__construct(ApiMain $mainModule, $moduleName, $modulePrefix='')
Definition ApiBase.php:235
setContinuationManager( $manager)
Set the continuation manager.
Definition ApiBase.php:664
getProfileTime()
Definition ApiBase.php:2408
addError( $msg, $code=null, $data=null)
Add an error for this module without aborting.
Definition ApiBase.php:1767
getExamplesMessages()
Returns usage examples for this module.
Definition ApiBase.php:301
extractRequestParams( $parseLimit=true)
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:718
addMessagesFromStatus(StatusValue $status, $types=[ 'warning', 'error'])
Add warnings and/or errors from a Status.
Definition ApiBase.php:1779
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:160
addDeprecation( $msg, $feature, $data=[])
Add a deprecation warning for this module.
Definition ApiBase.php:1734
dieUsageMsg( $error)
Output the error message related to a certain array.
Definition ApiBase.php:2725
setWarning( $warning)
Definition ApiBase.php:2450
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition ApiBase.php:364
getModuleSourceInfo()
Returns information about the source of this module, if known.
Definition ApiBase.php:2223
const PARAM_SUBMODULE_PARAM_PREFIX
(string) When PARAM_TYPE is 'submodule', used to indicate the 'g' prefix added by ApiQueryGeneratorBa...
Definition ApiBase.php:175
static escapeWikiText( $v)
A subset of wfEscapeWikiText for BC texts.
Definition ApiBase.php:1618
const PARAM_MIN
(integer) Lowest value allowed for the parameter, for PARAM_TYPE 'integer' and 'limit'.
Definition ApiBase.php:103
profileDBOut()
Definition ApiBase.php:2423
mustBePosted()
Indicates whether this module must be called with a POST request.
Definition ApiBase.php:405
getWebUITokenSalt(array $params)
Fetch the salt used in the Web UI corresponding to this module.
Definition ApiBase.php:459
static array $extensionInfo
Maps extension paths to info arrays.
Definition ApiBase.php:219
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:203
isMain()
Returns true if this module is the main module ($this === $this->mMainModule), false otherwise.
Definition ApiBase.php:515
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition ApiBase.php:209
handleParamNormalization( $paramName, $value, $rawValue)
Handle when a parameter was Unicode-normalized.
Definition ApiBase.php:1283
ApiMain $mMainModule
Definition ApiBase.php:222
validateLimit( $paramName, &$value, $min, $max, $botMax=null, $enforceLimits=false)
Validate the value against the minimum and user/bot maximum limits.
Definition ApiBase.php:1407
getFinalDescription()
Get final module description, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2005
string $mModuleName
Definition ApiBase.php:224
warnOrDie(ApiMessage $msg, $enforceLimits=false)
Adds a warning to the output, else dies.
Definition ApiBase.php:1820
static truncateArray(&$arr, $limit)
Truncate an array to a certain length.
Definition ApiBase.php:1572
string $mModulePrefix
Definition ApiBase.php:224
profileOut()
Definition ApiBase.php:2392
getResult()
Get the result object.
Definition ApiBase.php:610
requireMaxOneParameter( $params, $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:792
needsToken()
Returns the token type this module requires in order to execute.
Definition ApiBase.php:446
getDescriptionMessage()
Return the description message.
Definition ApiBase.php:1994
getParameterFromSettings( $paramName, $paramSettings, $parseLimit)
Using the settings determine the value for the given parameter.
Definition ApiBase.php:1002
getModulePath()
Get the path to this module.
Definition ApiBase.php:554
const PARAM_RANGE_ENFORCE
(boolean) For PARAM_TYPE 'integer', enforce PARAM_MIN and PARAM_MAX?
Definition ApiBase.php:121
logFeatureUsage( $feature)
Write logging information for API features to a debug log, for usage analysis.
Definition ApiBase.php:1972
const PARAM_EXTRA_NAMESPACES
(int[]) When PARAM_TYPE is 'namespace', include these as additional possible values.
Definition ApiBase.php:189
shouldCheckMaxlag()
Indicates if this module needs maxlag to be checked.
Definition ApiBase.php:374
getHelpFlags()
Generates the list of flags for the help screen and for action=paraminfo.
Definition ApiBase.php:2190
const LIMIT_SML1
Slow query, standard limit.
Definition ApiBase.php:207
dieUsageMsgOrDebug( $error)
Will only set a warning instead of failing if the global $wgDebugAPI is set to true.
Definition ApiBase.php:2737
getFinalParams( $flags=0)
Get final list of parameters, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2041
getWatchlistValue( $watchlist, $titleObj, $userOption=null)
Return true if we're to watch the page, false if not, null if no change.
Definition ApiBase.php:960
static $messageMap
Definition ApiBase.php:2528
isReadMode()
Indicates whether this module requires read rights.
Definition ApiBase.php:382
getExamples()
Returns usage examples for this module.
Definition ApiBase.php:2367
dieWithException( $exception, array $options=[])
Abort execution with an error derived from an exception.
Definition ApiBase.php:1808
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:128
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition ApiBase.php:1720
requireAtLeastOneParameter( $params, $required)
Die if none of a certain set of parameters is set and not false.
Definition ApiBase.php:820
getTitleFromTitleOrPageId( $params)
Get a Title object from a title or pageid param, if possible.
Definition ApiBase.php:932
const PARAM_ALL
(boolean|string) When PARAM_TYPE has a defined set of values and PARAM_ISMULTI is true,...
Definition ApiBase.php:183
const GET_VALUES_FOR_HELP
getAllowedParams() flag: When set, the result could take longer to generate, but should be more thoro...
Definition ApiBase.php:216
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:205
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:490
getTitleOrPageId( $params, $load=false)
Get a WikiPage object from a title or pageid param, if possible.
Definition ApiBase.php:895
validateTimestamp( $value, $encParamName)
Validate and normalize of parameters of type 'timestamp'.
Definition ApiBase.php:1460
dieStatus(StatusValue $status)
Throw an ApiUsageException based on the Status object.
Definition ApiBase.php:1861
useTransactionalTimeLimit()
Call wfTransactionalTimeLimit() if this request was POSTed.
Definition ApiBase.php:2440
getFinalParamDescription()
Get final parameter descriptions, after hooks have had a chance to tweak it as needed.
Definition ApiBase.php:2073
checkTitleUserPermissions(Title $title, $actions, $user=null)
Helper function for permission-denied errors.
Definition ApiBase.php:1908
getProfileDBTime()
Definition ApiBase.php:2431
dieBlocked(Block $block)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:1836
const ALL_DEFAULT_STRING
Definition ApiBase.php:200
dynamicParameterDocumentation()
Indicate if the module supports dynamically-determined parameters that cannot be included in self::ge...
Definition ApiBase.php:688
getModuleProfileName( $db=false)
Definition ApiBase.php:2376
getContinuationManager()
Get the continuation manager.
Definition ApiBase.php:650
explodeMultiValue( $value, $limit)
Split a multi-valued parameter string, like explode()
Definition ApiBase.php:1295
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
dieUsage( $description, $errorCode, $httpRespCode=0, $extradata=null)
Throw an ApiUsageException, which will (if uncaught) call the main module's error handler and die wit...
Definition ApiBase.php:2468
validateToken( $token, array $params)
Validate the supplied token.
Definition ApiBase.php:1497
getConditionalRequestData( $condition)
Returns data for HTTP conditional request mechanisms.
Definition ApiBase.php:475
parseMultiValue( $valueName, $value, $allowMultiple, $allowedValues, $allSpecifier=null)
Return an array of values that were given in a 'a|b|c' notation, after it optionally validates them a...
Definition ApiBase.php:1321
profileDBIn()
Definition ApiBase.php:2416
requireOnlyOneParameter( $params, $required)
Die if none or more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:754
profileIn()
Definition ApiBase.php:2384
const PARAM_ISMULTI
(boolean) Accept multiple pipe-separated values for this parameter (e.g.
Definition ApiBase.php:55
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition ApiBase.php:538
static stripMarkup( $text)
Turn wikitext into something resembling plaintext.
Message subclass that prepends wikitext for API help.
This is the main API class, used for both external and internal processing.
Definition ApiMain.php:45
Extension of Message implementing IApiMessage.
static create( $msg, $code=null, array $data=null)
Create an IApiMessage for the message.
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.
Exception used to abort API execution with an error.
static newWithMessage(ApiBase $module=null, $msg, $code=null, $data=null, $httpCode=0)
getType()
Get the type of target for this particular block.
Definition Block.php:1391
const TYPE_AUTO
Definition Block.php:86
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
Variant of the Message class.
Definition Message.php:1379
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Represents a title within MediaWiki.
Definition Title.php:39
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:50
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:158
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition WikiPage.php:120
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:64
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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 please use GetContentModels hook to make them known to core 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:1143
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
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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:1102
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:865
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:964
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:2753
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 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:2604
error also a ContextSource you ll probably need to make sure the header is varied on $request
Definition hooks.txt:2723
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:1966
Allows to change the fields on the form that will be generated $name
Definition hooks.txt:304
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup 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
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.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:40
$help
Definition mcc.php:32
const DB_REPLICA
Definition defines.php:25
$params