* Module parameters: Derived classes can define getAllowedParams() to specify
* which parameters to expect, how to parse and validate them.
*
- * Profiling: various methods to allow keeping tabs on various tasks and their
- * time costs
- *
* Self-documentation: code to allow the API to document its own state
*
* @ingroup API
const PARAM_RANGE_ENFORCE = 9;
/// @since 1.25
// Specify an alternative i18n message for this help parameter.
- // Value can be a string key, an array giving key and parameters, or a
- // Message object.
+ // Value is $msg for ApiBase::makeMessage()
const PARAM_HELP_MSG = 10;
/// @since 1.25
// Specify additional i18n messages to append to the normal message. Value
- // is an array of any of strings giving the message key, arrays giving key and
- // parameters, or Message objects.
+ // is an array of $msg for ApiBase::makeMessage()
const PARAM_HELP_MSG_APPEND = 11;
/// @since 1.25
// Specify additional information tags for the parameter. Value is an array
// comma-joined list of values, $3 = module prefix.
const PARAM_HELP_MSG_INFO = 12;
/// @since 1.25
- // When PARAM_DFLT is an array, this may be an array mapping those values
+ // When PARAM_TYPE is an array, this may be an array mapping those values
// to page titles which will be linked in the help.
const PARAM_VALUE_LINKS = 13;
+ /// @since 1.25
+ // When PARAM_TYPE is an array, this is an array mapping those values to
+ // $msg for ApiBase::makeMessage(). Any value not having a mapping will use
+ // apihelp-{$path}-paramvalue-{$param}-{$value} is used.
+ const PARAM_HELP_MSG_PER_VALUE = 14;
const LIMIT_BIG1 = 500; // Fast query, std user limit
const LIMIT_BIG2 = 5000; // Fast query, bot/sysop limit
*/
const GET_VALUES_FOR_HELP = 1;
+ /** @var array Maps extension paths to info arrays */
+ private static $extensionInfo = null;
+
/** @var ApiMain */
private $mMainModule;
/** @var string */
private $mModuleName, $mModulePrefix;
private $mSlaveDB = null;
private $mParamCache = array();
+ /** @var array|null|bool */
+ private $mModuleSource = false;
/**
* @param ApiMain $mainModule
* If the module may only be used with a certain format module,
* it should override this method to return an instance of that formatter.
* A value of null means the default format will be used.
+ * @note Do not use this just because you don't want to support non-json
+ * formats. This should be used only when there is a fundamental
+ * requirement for a specific format.
* @return mixed Instance of a derived class of ApiFormatBase, or null
*/
public function getCustomPrinter() {
return $this->isMain() ? null : $this->getMain();
}
+ /**
+ * Returns true if the current request breaks the same-origin policy.
+ *
+ * For example, json with callbacks.
+ *
+ * https://en.wikipedia.org/wiki/Same-origin_policy
+ *
+ * @since 1.25
+ * @return bool
+ */
+ public function lacksSameOriginSecurity() {
+ return $this->getMain()->getRequest()->getVal( 'callback' ) !== null;
+ }
+
/**
* Get the path to this module
*
*/
protected function getDB() {
if ( !isset( $this->mSlaveDB ) ) {
- $this->profileDBIn();
$this->mSlaveDB = wfGetDB( DB_SLAVE, 'api' );
- $this->profileDBOut();
}
return $this->mSlaveDB;
* @param string $token Supplied token
* @param array $params All supplied parameters for the module
* @return bool
+ * @throws MWException
*/
final public function validateToken( $token, array $params ) {
$tokenType = $this->needsToken();
* @throws UsageException
*/
public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) {
- Profiler::instance()->close();
throw new UsageException(
$description,
$this->encodeParamName( $errorCode ),
* @since 1.23
* @param Status $status
* @return array Array of code and error string
+ * @throws MWException
*/
public function getErrorFromStatus( $status ) {
if ( $status->isGood() ) {
throw new MWException( "Internal error in $method: $message" );
}
+ /**
+ * Write logging information for API features to a debug log, for usage
+ * analysis.
+ * @param string $feature Feature being used.
+ */
+ protected function logFeatureUsage( $feature ) {
+ $request = $this->getRequest();
+ $s = '"' . addslashes( $feature ) . '"' .
+ ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
+ ' "' . $request->getIP() . '"' .
+ ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
+ ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
+ wfDebugLog( 'api-feature-usage', $s, 'private' );
+ }
+
/**@}*/
/************************************************************************//**
* @return array Keys are parameter names, values are arrays of Message objects
*/
public function getFinalParamDescription() {
+ $prefix = $this->getModulePrefix();
+ $name = $this->getModuleName();
+ $path = $this->getModulePath();
+
$desc = $this->getParamDescription();
Hooks::run( 'APIGetParamDescription', array( &$this, &$desc ) );
if ( isset( $settings[ApiBase::PARAM_HELP_MSG] ) ) {
$msg = $settings[ApiBase::PARAM_HELP_MSG];
} else {
- $msg = $this->msg( "apihelp-{$this->getModulePath()}-param-{$param}" );
+ $msg = $this->msg( "apihelp-{$path}-param-{$param}" );
if ( !$msg->exists() ) {
$msg = $this->msg( 'api-help-fallback-parameter', $d );
}
}
- $msg = ApiBase::makeMessage( $msg, $this->getContext(), array(
- $this->getModulePrefix(),
- $param,
- $this->getModuleName(),
- $this->getModulePath(),
- ) );
+ $msg = ApiBase::makeMessage( $msg, $this->getContext(),
+ array( $prefix, $param, $name, $path ) );
if ( !$msg ) {
$this->dieDebug( __METHOD__,
'Value in ApiBase::PARAM_HELP_MSG is not valid' );
}
$msgs[$param] = array( $msg );
+ if ( isset( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
+ if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE] ) ) {
+ $this->dieDebug( __METHOD__,
+ 'ApiBase::PARAM_HELP_MSG_PER_VALUE is not valid' );
+ }
+ if ( !is_array( $settings[ApiBase::PARAM_TYPE] ) ) {
+ $this->dieDebug( __METHOD__,
+ 'ApiBase::PARAM_HELP_MSG_PER_VALUE may only be used when ' .
+ 'ApiBase::PARAM_TYPE is an array' );
+ }
+
+ $valueMsgs = $settings[ApiBase::PARAM_HELP_MSG_PER_VALUE];
+ foreach ( $settings[ApiBase::PARAM_TYPE] as $value ) {
+ if ( isset( $valueMsgs[$value] ) ) {
+ $msg = $valueMsgs[$value];
+ } else {
+ $msg = "apihelp-{$path}-paramvalue-{$param}-{$value}";
+ }
+ $m = ApiBase::makeMessage( $msg, $this->getContext(),
+ array( $prefix, $param, $name, $path, $value ) );
+ if ( $m ) {
+ $m = new ApiHelpParamValueMessage(
+ $value,
+ array( $m->getKey(), 'api-help-param-no-description' ),
+ $m->getParams()
+ );
+ $msgs[$param][] = $m->setContext( $this->getContext() );
+ } else {
+ $this->dieDebug( __METHOD__,
+ "Value in ApiBase::PARAM_HELP_MSG_PER_VALUE for $value is not valid" );
+ }
+ }
+ }
+
if ( isset( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
if ( !is_array( $settings[ApiBase::PARAM_HELP_MSG_APPEND] ) ) {
$this->dieDebug( __METHOD__,
'Value for ApiBase::PARAM_HELP_MSG_APPEND is not an array' );
}
foreach ( $settings[ApiBase::PARAM_HELP_MSG_APPEND] as $m ) {
- $m = ApiBase::makeMessage( $m, $this->getContext(), array(
- $this->getModulePrefix(),
- $param,
- $this->getModuleName(),
- $this->getModulePath(),
- ) );
+ $m = ApiBase::makeMessage( $m, $this->getContext(),
+ array( $prefix, $param, $name, $path ) );
if ( $m ) {
$msgs[$param][] = $m;
} else {
}
/**
- * Called from ApiHelp before the pieces are joined together and returned.
+ * Returns information about the source of this module, if known
*
- * This exists mainly for ApiMain to add the Permissions and Credits
- * sections. Other modules probably don't need it.
+ * Returned array is an array with the following keys:
+ * - path: Install path
+ * - name: Extension name, or "MediaWiki" for core
+ * - namemsg: (optional) i18n message key for a display name
+ * - license-name: (optional) Name of license
*
- * @param string[] &$help Array of help data
- * @param array $options Options passed to ApiHelp::getHelp
- */
- public function modifyHelp( array &$help, array $options ) {
- }
-
- /**@}*/
-
- /************************************************************************//**
- * @name Profiling
- * @{
+ * @return array|null
*/
+ protected function getModuleSourceInfo() {
+ global $IP;
- /**
- * Profiling: total module execution time
- */
- private $mTimeIn = 0, $mModuleTime = 0;
-
- /**
- * Get the name of the module as shown in the profiler log
- *
- * @param DatabaseBase|bool $db
- *
- * @return string
- */
- public function getModuleProfileName( $db = false ) {
- if ( $db ) {
- return 'API:' . $this->mModuleName . '-DB';
+ if ( $this->mModuleSource !== false ) {
+ return $this->mModuleSource;
}
- return 'API:' . $this->mModuleName;
- }
-
- /**
- * Start module profiling
- */
- public function profileIn() {
- if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileOut()' );
+ // First, try to find where the module comes from...
+ $rClass = new ReflectionClass( $this );
+ $path = $rClass->getFileName();
+ if ( !$path ) {
+ // No path known?
+ $this->mModuleSource = null;
+ return null;
}
- $this->mTimeIn = microtime( true );
- wfProfileIn( $this->getModuleProfileName() );
- }
+ $path = realpath( $path ) ?: $path;
- /**
- * End module profiling
- */
- public function profileOut() {
- if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileIn() first' );
- }
- if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug(
- __METHOD__,
- 'Must be called after database profiling is done with profileDBOut()'
+ // Build map of extension directories to extension info
+ if ( self::$extensionInfo === null ) {
+ self::$extensionInfo = array(
+ realpath( __DIR__ ) ?: __DIR__ => array(
+ 'path' => $IP,
+ 'name' => 'MediaWiki',
+ 'license-name' => 'GPL-2.0+',
+ ),
+ realpath( "$IP/extensions" ) ?: "$IP/extensions" => null,
);
- }
-
- $this->mModuleTime += microtime( true ) - $this->mTimeIn;
- $this->mTimeIn = 0;
- wfProfileOut( $this->getModuleProfileName() );
- }
-
- /**
- * When modules crash, sometimes it is needed to do a profileOut() regardless
- * of the profiling state the module was in. This method does such cleanup.
- */
- public function safeProfileOut() {
- if ( $this->mTimeIn !== 0 ) {
- if ( $this->mDBTimeIn !== 0 ) {
- $this->profileDBOut();
- }
- $this->profileOut();
- }
- }
-
- /**
- * Total time the module was executed
- * @return float
- */
- public function getProfileTime() {
- if ( $this->mTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileOut() first' );
- }
-
- return $this->mModuleTime;
- }
-
- /**
- * Profiling: database execution time
- */
- private $mDBTimeIn = 0, $mDBTime = 0;
-
- /**
- * Start module profiling
- */
- public function profileDBIn() {
- if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug(
- __METHOD__,
- 'Must be called while profiling the entire module with profileIn()'
+ $keep = array(
+ 'path' => null,
+ 'name' => null,
+ 'namemsg' => null,
+ 'license-name' => null,
);
- }
- if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called twice without calling profileDBOut()' );
- }
- $this->mDBTimeIn = microtime( true );
- wfProfileIn( $this->getModuleProfileName( true ) );
- }
+ foreach ( $this->getConfig()->get( 'ExtensionCredits' ) as $group ) {
+ foreach ( $group as $ext ) {
+ if ( !isset( $ext['path'] ) || !isset( $ext['name'] ) ) {
+ // This shouldn't happen, but does anyway.
+ continue;
+ }
- /**
- * End database profiling
- */
- public function profileDBOut() {
- if ( $this->mTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Must be called while profiling ' .
- 'the entire module with profileIn()' );
- }
- if ( $this->mDBTimeIn === 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBIn() first' );
+ $extpath = $ext['path'];
+ if ( !is_dir( $extpath ) ) {
+ $extpath = dirname( $extpath );
+ }
+ self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
+ array_intersect_key( $ext, $keep );
+ }
+ }
+ foreach ( ExtensionRegistry::getInstance()->getAllThings() as $ext ) {
+ $extpath = $ext['path'];
+ if ( !is_dir( $extpath ) ) {
+ $extpath = dirname( $extpath );
+ }
+ self::$extensionInfo[realpath( $extpath ) ?: $extpath] =
+ array_intersect_key( $ext, $keep );
+ }
}
- $time = microtime( true ) - $this->mDBTimeIn;
- $this->mDBTimeIn = 0;
+ // Now traverse parent directories until we find a match or run out of
+ // parents.
+ do {
+ if ( array_key_exists( $path, self::$extensionInfo ) ) {
+ // Found it!
+ $this->mModuleSource = self::$extensionInfo[$path];
+ return $this->mModuleSource;
+ }
- $this->mDBTime += $time;
- $this->getMain()->mDBTime += $time;
- wfProfileOut( $this->getModuleProfileName( true ) );
- }
+ $oldpath = $path;
+ $path = dirname( $path );
+ } while ( $path !== $oldpath );
- /**
- * Total time the module used the database
- * @return float
- */
- public function getProfileDBTime() {
- if ( $this->mDBTimeIn !== 0 ) {
- ApiBase::dieDebug( __METHOD__, 'Called without calling profileDBOut() first' );
- }
-
- return $this->mDBTime;
+ // No idea what extension this might be.
+ $this->mModuleSource = null;
+ return null;
}
/**
- * Write logging information for API features to a debug log, for usage
- * analysis.
- * @param string $feature Feature being used.
+ * Called from ApiHelp before the pieces are joined together and returned.
+ *
+ * This exists mainly for ApiMain to add the Permissions and Credits
+ * sections. Other modules probably don't need it.
+ *
+ * @param string[] &$help Array of help data
+ * @param array $options Options passed to ApiHelp::getHelp
*/
- protected function logFeatureUsage( $feature ) {
- $request = $this->getRequest();
- $s = '"' . addslashes( $feature ) . '"' .
- ' "' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . '"' .
- ' "' . $request->getIP() . '"' .
- ' "' . addslashes( $request->getHeader( 'Referer' ) ) . '"' .
- ' "' . addslashes( $this->getMain()->getUserAgent() ) . '"';
- wfDebugLog( 'api-feature-usage', $s, 'private' );
+ public function modifyHelp( array &$help, array $options ) {
}
/**@}*/
return false;
}
+ /**
+ * @deprecated since 1.25, always returns empty string
+ * @param DatabaseBase|bool $db
+ * @return string
+ */
+ public function getModuleProfileName( $db = false ) {
+ wfDeprecated( __METHOD__, '1.25' );
+ return '';
+ }
+
+ /**
+ * @deprecated since 1.25
+ */
+ public function profileIn() {
+ // No wfDeprecated() yet because extensions call this and might need to
+ // keep doing so for BC.
+ }
+
+ /**
+ * @deprecated since 1.25
+ */
+ public function profileOut() {
+ // No wfDeprecated() yet because extensions call this and might need to
+ // keep doing so for BC.
+ }
+
+ /**
+ * @deprecated since 1.25
+ */
+ public function safeProfileOut() {
+ wfDeprecated( __METHOD__, '1.25' );
+ }
+
+ /**
+ * @deprecated since 1.25, always returns 0
+ * @return float
+ */
+ public function getProfileTime() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return 0;
+ }
+
+ /**
+ * @deprecated since 1.25
+ */
+ public function profileDBIn() {
+ wfDeprecated( __METHOD__, '1.25' );
+ }
+
+ /**
+ * @deprecated since 1.25
+ */
+ public function profileDBOut() {
+ wfDeprecated( __METHOD__, '1.25' );
+ }
+
+ /**
+ * @deprecated since 1.25, always returns 0
+ * @return float
+ */
+ public function getProfileDBTime() {
+ wfDeprecated( __METHOD__, '1.25' );
+ return 0;
+ }
+
/**@}*/
}