From ca32f08966f1b51fcb19460f0996bb0c4048e6fe Mon Sep 17 00:00:00 2001 From: Pierre Schmitz Date: Sat, 3 Dec 2011 13:29:22 +0100 Subject: Update to MediaWiki 1.18.0 * also update ArchLinux skin to chagnes in MonoBook * Use only css to hide our menu bar when printing --- includes/api/ApiBase.php | 276 ++++++++++++++++++++------- includes/api/ApiBlock.php | 85 +++++---- includes/api/ApiComparePages.php | 130 +++++++++++++ includes/api/ApiDelete.php | 42 +++-- includes/api/ApiDisabled.php | 6 +- includes/api/ApiEditPage.php | 105 ++++++----- includes/api/ApiEmailUser.php | 8 +- includes/api/ApiExpandTemplates.php | 21 ++- includes/api/ApiFeedContributions.php | 207 ++++++++++++++++++++ includes/api/ApiFeedWatchlist.php | 41 +++- includes/api/ApiFileRevert.php | 189 +++++++++++++++++++ includes/api/ApiFormatBase.php | 22 ++- includes/api/ApiFormatDbg.php | 6 +- includes/api/ApiFormatDump.php | 4 +- includes/api/ApiFormatJson.php | 4 +- includes/api/ApiFormatPhp.php | 4 +- includes/api/ApiFormatRaw.php | 8 +- includes/api/ApiFormatTxt.php | 6 +- includes/api/ApiFormatWddx.php | 4 +- includes/api/ApiFormatXml.php | 24 ++- includes/api/ApiFormatYaml.php | 14 +- includes/api/ApiHelp.php | 17 +- includes/api/ApiImport.php | 38 +++- includes/api/ApiLogin.php | 25 ++- includes/api/ApiLogout.php | 10 +- includes/api/ApiMain.php | 256 ++++++++++++++++--------- includes/api/ApiMove.php | 49 +++-- includes/api/ApiOpenSearch.php | 12 +- includes/api/ApiPageSet.php | 127 ++++++++----- includes/api/ApiParamInfo.php | 83 +++++--- includes/api/ApiParse.php | 209 ++++++++++++--------- includes/api/ApiPatrol.php | 10 +- includes/api/ApiProtect.php | 24 ++- includes/api/ApiPurge.php | 47 ++++- includes/api/ApiQuery.php | 74 +++++--- includes/api/ApiQueryAllCategories.php | 28 ++- includes/api/ApiQueryAllLinks.php | 31 +-- includes/api/ApiQueryAllUsers.php | 189 +++++++++++++++---- includes/api/ApiQueryAllimages.php | 68 +++++-- includes/api/ApiQueryAllmessages.php | 135 +++++++++---- includes/api/ApiQueryAllpages.php | 74 ++++++-- includes/api/ApiQueryBacklinks.php | 80 +++++--- includes/api/ApiQueryBase.php | 104 +++++++++-- includes/api/ApiQueryBlocks.php | 86 ++++----- includes/api/ApiQueryCategories.php | 14 +- includes/api/ApiQueryCategoryInfo.php | 8 +- includes/api/ApiQueryCategoryMembers.php | 88 ++++++--- includes/api/ApiQueryDeletedrevs.php | 117 +++++++----- includes/api/ApiQueryDisabled.php | 6 +- includes/api/ApiQueryDuplicateFiles.php | 14 +- includes/api/ApiQueryExtLinksUsage.php | 109 +++++++---- includes/api/ApiQueryExternalLinks.php | 52 +++++- includes/api/ApiQueryFilearchive.php | 113 +++++++---- includes/api/ApiQueryIWBacklinks.php | 13 +- includes/api/ApiQueryIWLinks.php | 33 +++- includes/api/ApiQueryImageInfo.php | 227 ++++++++++++++++------ includes/api/ApiQueryImages.php | 28 ++- includes/api/ApiQueryInfo.php | 50 ++++- includes/api/ApiQueryLangBacklinks.php | 220 ++++++++++++++++++++++ includes/api/ApiQueryLangLinks.php | 39 +++- includes/api/ApiQueryLinks.php | 18 +- includes/api/ApiQueryLogEvents.php | 82 +++++--- includes/api/ApiQueryPageProps.php | 62 +++--- includes/api/ApiQueryProtectedTitles.php | 32 +++- includes/api/ApiQueryQueryPage.php | 198 ++++++++++++++++++++ includes/api/ApiQueryRandom.php | 22 ++- includes/api/ApiQueryRecentChanges.php | 132 ++++++++----- includes/api/ApiQueryRevisions.php | 57 +++--- includes/api/ApiQuerySearch.php | 38 ++-- includes/api/ApiQuerySiteinfo.php | 140 +++++++++++--- includes/api/ApiQueryStashImageInfo.php | 93 ++++----- includes/api/ApiQueryTags.php | 20 +- includes/api/ApiQueryUserContributions.php | 30 ++- includes/api/ApiQueryUserInfo.php | 41 +++- includes/api/ApiQueryUsers.php | 97 ++++++---- includes/api/ApiQueryWatchlist.php | 94 ++++++---- includes/api/ApiQueryWatchlistRaw.php | 14 +- includes/api/ApiResult.php | 35 +++- includes/api/ApiRollback.php | 24 ++- includes/api/ApiRsd.php | 15 +- includes/api/ApiUnblock.php | 46 +++-- includes/api/ApiUndelete.php | 19 +- includes/api/ApiUpload.php | 291 +++++++++++++++++------------ includes/api/ApiUserrights.php | 17 +- includes/api/ApiWatch.php | 33 +++- 85 files changed, 4222 insertions(+), 1541 deletions(-) create mode 100644 includes/api/ApiComparePages.php create mode 100644 includes/api/ApiFeedContributions.php create mode 100644 includes/api/ApiFileRevert.php create mode 100644 includes/api/ApiQueryLangBacklinks.php create mode 100644 includes/api/ApiQueryQueryPage.php (limited to 'includes/api') diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 175fa6a1..30e42934 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -1,6 +1,6 @@ getResult()->getData(); } + /** + * Create a new RequestContext object to use e.g. for calls to other parts + * the software. + * The object will have the WebRequest and the User object set to the ones + * used in this instance. + * + * @return RequestContext + */ + public function createContext() { + global $wgUser; + + $context = new RequestContext; + $context->setRequest( $this->getMain()->getRequest() ); + $context->setUser( $wgUser ); /// @todo FIXME: we should store the User object + + return $context; + } + /** * Set warning section for this module. Users should monitor this * section to notice any changes in API. Multiple calls to this @@ -178,7 +199,8 @@ abstract class ApiBase { * @param $warning string Warning message */ public function setWarning( $warning ) { - $data = $this->getResult()->getData(); + $result = $this->getResult(); + $data = $result->getData(); if ( isset( $data['warnings'][$this->getModuleName()] ) ) { // Don't add duplicate warnings $warn_regex = preg_quote( $warning, '/' ); @@ -188,13 +210,13 @@ abstract class ApiBase { $oldwarning = $data['warnings'][$this->getModuleName()]['*']; // If there is a warning already, append it to the existing one $warning = "$oldwarning\n$warning"; - $this->getResult()->unsetValue( 'warnings', $this->getModuleName() ); + $result->unsetValue( 'warnings', $this->getModuleName() ); } $msg = array(); ApiResult::setContent( $msg, $warning ); - $this->getResult()->disableSizeCheck(); - $this->getResult()->addValue( 'warnings', $this->getModuleName(), $msg ); - $this->getResult()->enableSizeCheck(); + $result->disableSizeCheck(); + $result->addValue( 'warnings', $this->getModuleName(), $msg ); + $result->enableSizeCheck(); } /** @@ -235,8 +257,7 @@ abstract class ApiBase { $msg .= "\nThis module only accepts POST requests"; } if ( $this->isReadMode() || $this->isWriteMode() || - $this->mustBePosted() ) - { + $this->mustBePosted() ) { $msg .= "\n"; } @@ -246,20 +267,8 @@ abstract class ApiBase { $msg .= "Parameters:\n$paramsMsg"; } - // Examples - $examples = $this->getExamples(); - if ( $examples !== false ) { - if ( !is_array( $examples ) ) { - $examples = array( - $examples - ); - } - - if ( count( $examples ) > 0 ) { - $msg .= 'Example' . ( count( $examples ) > 1 ? 's' : '' ) . ":\n "; - $msg .= implode( $lnPrfx, $examples ) . "\n"; - } - } + $msg .= $this->makeHelpArrayToString( $lnPrfx, "Example", $this->getExamples() ); + $msg .= $this->makeHelpArrayToString( $lnPrfx, "Help page", $this->getHelpUrls() ); if ( $this->getMain()->getShowVersions() ) { $versions = $this->getVersion(); @@ -282,10 +291,34 @@ abstract class ApiBase { return $msg; } + /** + * @param $prefix string Text to split output items + * @param $title string What is being output + * @param $input string|array + * @return string + */ + protected function makeHelpArrayToString( $prefix, $title, $input ) { + if ( $input === false ) { + return ''; + } + if ( !is_array( $input ) ) { + $input = array( + $input + ); + } + + if ( count( $input ) > 0 ) { + $msg = $title . ( count( $input ) > 1 ? 's' : '' ) . ":\n "; + $msg .= implode( $prefix, $input ) . "\n"; + return $msg; + } + return ''; + } + /** * Generates the parameter descriptions for this module, to be displayed in the * module's help. - * @return string + * @return string or false */ public function makeHelpMsgParameters() { $params = $this->getFinalParams(); @@ -293,7 +326,8 @@ abstract class ApiBase { $paramsDescription = $this->getFinalParamDescription(); $msg = ''; - $paramPrefix = "\n" . str_repeat( ' ', 19 ); + $paramPrefix = "\n" . str_repeat( ' ', 24 ); + $descWordwrap = "\n" . str_repeat( ' ', 28 ); foreach ( $params as $paramName => $paramSettings ) { $desc = isset( $paramsDescription[$paramName] ) ? $paramsDescription[$paramName] : ''; if ( is_array( $desc ) ) { @@ -336,12 +370,16 @@ abstract class ApiBase { $choices[] = $t; } } - $desc .= $paramPrefix . $nothingPrompt . $prompt . implode( ', ', $choices ); + $desc .= $paramPrefix . $nothingPrompt . $prompt; + $choicesstring = implode( ', ', $choices ); + $desc .= wordwrap( $choicesstring, 100, $descWordwrap ); } else { switch ( $type ) { case 'namespace': // Special handling because namespaces are type-limited, yet they are not given - $desc .= $paramPrefix . $prompt . implode( ', ', MWNamespace::getValidNamespaces() ); + $desc .= $paramPrefix . $prompt; + $desc .= wordwrap( implode( ', ', MWNamespace::getValidNamespaces() ), + 100, $descWordwrap ); break; case 'limit': $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}"; @@ -371,7 +409,7 @@ abstract class ApiBase { $isArray = is_array( $paramSettings[self::PARAM_TYPE] ); if ( !$isArray - || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1) { + || $isArray && count( $paramSettings[self::PARAM_TYPE] ) > self::LIMIT_SML1 ) { $desc .= $paramPrefix . "Maximum number of values " . self::LIMIT_SML1 . " (" . self::LIMIT_SML2 . " for bots)"; } @@ -386,7 +424,7 @@ abstract class ApiBase { $desc .= $paramPrefix . "Default: $default"; } - $msg .= sprintf( " %-14s - %s\n", $this->encodeParamName( $paramName ), $desc ); + $msg .= sprintf( " %-19s - %s\n", $this->encodeParamName( $paramName ), $desc ); } return $msg; @@ -398,9 +436,13 @@ abstract class ApiBase { /** * Callback for preg_replace_callback() call in makeHelpMsg(). * Replaces a source file name with a link to ViewVC + * + * @return string */ public function makeHelpMsg_callback( $matches ) { global $wgAutoloadClasses, $wgAutoloadLocalClasses; + + $file = ''; if ( isset( $wgAutoloadLocalClasses[get_class( $this )] ) ) { $file = $wgAutoloadLocalClasses[get_class( $this )]; } elseif ( isset( $wgAutoloadClasses[get_class( $this )] ) ) { @@ -421,7 +463,7 @@ abstract class ApiBase { // This is necessary to make stuff like ApiMain::getVersion() // returning the version string for ApiBase work if ( $path ) { - return "{$matches[0]}\n http://svn.wikimedia.org/" . + return "{$matches[0]}\n https://svn.wikimedia.org/" . "viewvc/mediawiki/trunk/" . dirname( $path ) . "/{$matches[2]}"; } @@ -437,8 +479,8 @@ abstract class ApiBase { } /** - * Returns usage examples for this module. Return null if no examples are available. - * @return mixed string or array of strings + * Returns usage examples for this module. Return false if no examples are available. + * @return false|string|array */ protected function getExamples() { return false; @@ -449,7 +491,7 @@ abstract class ApiBase { * value) or (parameter name) => (array with PARAM_* constants as keys) * Don't call this function directly: use getFinalParams() to allow * hooks to modify parameters as needed. - * @return array + * @return array or false */ protected function getAllowedParams() { return false; @@ -459,7 +501,7 @@ abstract class ApiBase { * Returns an array of parameter descriptions. * Don't call this functon directly: use getFinalParamDescription() to * allow hooks to modify descriptions as needed. - * @return array + * @return array or false */ protected function getParamDescription() { return false; @@ -468,7 +510,7 @@ abstract class ApiBase { /** * Get final list of parameters, after hooks have had a chance to * tweak it as needed. - * @return array + * @return array or false */ public function getFinalParams() { $params = $this->getAllowedParams(); @@ -553,6 +595,54 @@ abstract class ApiBase { } } + /** + * Generates the possible errors requireOnlyOneParameter() can die with + * + * @param $params array + * @return array + */ + public function getRequireOnlyOneParameterErrorMessages( $params ) { + $p = $this->getModulePrefix(); + $params = implode( ", {$p}", $params ); + + return array( + array( 'code' => "{$p}missingparam", 'info' => "One of the parameters {$p}{$params} is required" ), + array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" ) + ); + } + + /** + * Die if more than one of a certain set of parameters is set and not false. + * + * @param $params array + */ + public function requireMaxOneParameter( $params ) { + $required = func_get_args(); + array_shift( $required ); + + $intersection = array_intersect( array_keys( array_filter( $params, + array( $this, "parameterNotEmpty" ) ) ), $required ); + + if ( count( $intersection ) > 1 ) { + $this->dieUsage( 'The parameters ' . implode( ', ', $intersection ) . ' can not be used together', 'invalidparammix' ); + } + } + + /** + * Generates the possible error requireMaxOneParameter() can die with + * + * @param $params array + * @return array + */ + public function getRequireMaxOneParameterErrorMessages( $params ) { + $p = $this->getModulePrefix(); + $params = implode( ", {$p}", $params ); + + return array( + array( 'code' => "{$p}invalidparammix", 'info' => "The parameters {$p}{$params} can not be used together" ) + ); + } + /** * Callback function used in requireOnlyOneParameter to check whether reequired parameters are set * @@ -564,7 +654,7 @@ abstract class ApiBase { } /** - * @deprecated use MWNamespace::getValidNamespaces() + * @deprecated since 1.17 use MWNamespace::getValidNamespaces() */ public static function getValidNamespaces() { return MWNamespace::getValidNamespaces(); @@ -576,7 +666,7 @@ abstract class ApiBase { * @param $titleObj Title the page under consideration * @param $userOption String The user option to consider when $watchlist=preferences. * If not set will magically default to either watchdefault or watchcreations - * @returns Boolean + * @return bool */ protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) { @@ -617,17 +707,17 @@ abstract class ApiBase { * @param $titleObj Title the article's title to change * @param $userOption String The user option to consider when $watch=preferences */ - protected function setWatch ( $watch, $titleObj, $userOption = null ) { + protected function setWatch( $watch, $titleObj, $userOption = null ) { $value = $this->getWatchlistValue( $watch, $titleObj, $userOption ); if ( $value === null ) { return; } - $articleObj = new Article( $titleObj ); + global $wgUser; if ( $value ) { - $articleObj->doWatch(); + WatchAction::doWatch( $titleObj, $wgUser ); } else { - $articleObj->doUnwatch(); + WatchAction::doUnwatch( $titleObj, $wgUser ); } } @@ -707,16 +797,18 @@ abstract class ApiBase { $enforceLimits = isset ( $paramSettings[self::PARAM_RANGE_ENFORCE] ) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; - if ( !is_null( $min ) || !is_null( $max ) ) { - if ( is_array( $value ) ) { - $value = array_map( 'intval', $value ); - foreach ( $value as &$v ) { + if ( is_array( $value ) ) { + $value = array_map( 'intval', $value ); + if ( !is_null( $min ) || !is_null( $max ) ) { + foreach ( $value as &$v ) { $this->validateLimit( $paramName, $v, $min, $max, null, $enforceLimits ); } - } else { - $value = intval( $value ); - $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits ); - } + } + } else { + $value = intval( $value ); + if ( !is_null( $min ) || !is_null( $max ) ) { + $this->validateLimit( $paramName, $value, $min, $max, null, $enforceLimits ); + } } break; case 'limit': @@ -745,14 +837,13 @@ abstract class ApiBase { } break; case 'timestamp': - if ( $multi ) { - ApiBase::dieDebug( __METHOD__, "Multi-values not supported for $encParamName" ); - } - $value = wfTimestamp( TS_UNIX, $value ); - if ( $value === 0 ) { - $this->dieUsage( "Invalid value '$value' for timestamp parameter $encParamName", "badtimestamp_{$encParamName}" ); + if ( is_array( $value ) ) { + foreach ( $value as $key => $val ) { + $value[$key] = $this->validateTimestamp( $val, $encParamName ); + } + } else { + $value = $this->validateTimestamp( $value, $encParamName ); } - $value = wfTimestamp( TS_MW, $value ); break; case 'user': if ( !is_array( $value ) ) { @@ -785,7 +876,7 @@ abstract class ApiBase { if ( $deprecated && $value !== false ) { $this->setWarning( "The $encParamName parameter has been deprecated." ); } - } else if ( $required ) { + } elseif ( $required ) { $this->dieUsageMsg( array( 'missingparam', $paramName ) ); } @@ -848,8 +939,8 @@ abstract class ApiBase { * Prints usage info on failure. * @param $paramName string Parameter name * @param $value int Parameter value - * @param $min int Minimum value - * @param $max int Maximum value for users + * @param $min int|null Minimum value + * @param $max int|null Maximum value for users * @param $botMax int Maximum value for sysops/bots * @param $enforceLimits Boolean Whether to enforce (die) if value is outside limits */ @@ -883,6 +974,19 @@ abstract class ApiBase { } } + /** + * @param $value string + * @param $paramName string + * @return string + */ + function validateTimestamp( $value, $paramName ) { + $value = wfTimestamp( TS_UNIX, $value ); + if ( $value === 0 ) { + $this->dieUsage( "Invalid value '$value' for timestamp parameter $paramName", "badtimestamp_{$paramName}" ); + } + return wfTimestamp( TS_MW, $value ); + } + /** * Adds a warning to the output, else dies * @@ -924,7 +1028,7 @@ abstract class ApiBase { * @param $extradata array Data to add to the element; array in ApiResult format */ public function dieUsage( $description, $errorCode, $httpRespCode = 0, $extradata = null ) { - wfProfileClose(); + Profiler::instance()->close(); throw new UsageException( $description, $this->encodeParamName( $errorCode ), $httpRespCode, $extradata ); } @@ -940,7 +1044,8 @@ abstract class ApiBase { 'ns-specialprotected' => array( 'code' => 'unsupportednamespace', 'info' => "Pages in the Special namespace can't be edited" ), 'protectedinterface' => array( 'code' => 'protectednamespace-interface', 'info' => "You're not allowed to edit interface messages" ), 'namespaceprotected' => array( 'code' => 'protectednamespace', 'info' => "You're not allowed to edit pages in the ``\$1'' namespace" ), - 'customcssjsprotected' => array( 'code' => 'customcssjsprotected', 'info' => "You're not allowed to edit custom CSS and JavaScript pages" ), + 'customcssprotected' => array( 'code' => 'customcssprotected', 'info' => "You're not allowed to edit custom CSS pages" ), + 'customjsprotected' => array( 'code' => 'customjsprotected', 'info' => "You're not allowed to edit custom JavaScript pages" ), 'cascadeprotected' => array( 'code' => 'cascadeprotected', 'info' => "The page you're trying to edit is protected because it's included in a cascade-protected page" ), 'protectedpagetext' => array( 'code' => 'protectedpage', 'info' => "The ``\$1'' right is required to edit this page" ), 'protect-cantedit' => array( 'code' => 'cantedit', 'info' => "You can't protect this page because you can't edit it" ), @@ -1041,6 +1146,7 @@ abstract class ApiBase { 'sharedfile-exists' => array( 'code' => 'fileexists-sharedrepo-perm', 'info' => 'The target file exists on a shared repository. Use the ignorewarnings parameter to override it.' ), 'mustbeposted' => array( 'code' => 'mustbeposted', 'info' => "The \$1 module requires a POST request" ), 'show' => array( 'code' => 'show', 'info' => 'Incorrect parameter - mutually exclusive values may not be supplied' ), + 'specialpage-cantexecute' => array( 'code' => 'specialpage-cantexecute', 'info' => "You don't have permission to view the results of this special page" ), // ApiEditPage messages 'noimageredirect-anon' => array( 'code' => 'noimageredirect-anon', 'info' => "Anonymous users can't create image redirects" ), @@ -1058,12 +1164,22 @@ abstract class ApiBase { 'emptynewsection' => array( 'code' => 'emptynewsection', 'info' => 'Creating empty new sections is not possible.' ), 'revwrongpage' => array( 'code' => 'revwrongpage', 'info' => "r\$1 is not a revision of ``\$2''" ), 'undo-failure' => array( 'code' => 'undofailure', 'info' => 'Undo failed due to conflicting intermediate edits' ), + + // Messages from WikiPage::doEit() + 'edit-hook-aborted' => array( 'code' => 'edit-hook-aborted', 'info' => "Your edit was aborted by an ArticleSave hook" ), + 'edit-gone-missing' => array( 'code' => 'edit-gone-missing', 'info' => "The page you tried to edit doesn't seem to exist anymore" ), + 'edit-conflict' => array( 'code' => 'editconflict', 'info' => "Edit conflict detected" ), + 'edit-already-exists' => array( 'code' => 'edit-already-exists', 'info' => "It seems the page you tried to create already exist" ), // uploadMsgs 'invalid-session-key' => array( 'code' => 'invalid-session-key', 'info' => 'Not a valid session key' ), 'nouploadmodule' => array( 'code' => 'nouploadmodule', 'info' => 'No upload module set' ), 'uploaddisabled' => array( 'code' => 'uploaddisabled', 'info' => 'Uploads are not enabled. Make sure $wgEnableUploads is set to true in LocalSettings.php and the PHP ini setting file_uploads is true' ), 'copyuploaddisabled' => array( 'code' => 'copyuploaddisabled', 'info' => 'Uploads by URL is not enabled. Make sure $wgAllowCopyUploads is set to true in LocalSettings.php.' ), + + 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ), + 'illegal-filename' => array( 'code' => 'illegal-filename', 'info' => 'The filename is not allowed' ), + 'filetype-missing' => array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ), ); /** @@ -1077,9 +1193,14 @@ abstract class ApiBase { /** * Output the error message related to a certain array - * @param $error array Element of a getUserPermissionsErrors()-style array + * @param $error (array|string) Element of a getUserPermissionsErrors()-style array */ public function dieUsageMsg( $error ) { + # most of the time we send a 1 element, so we might as well send it as + # a string and make this an array here. + if( is_string( $error ) ) { + $error = array( $error ); + } $parsed = $this->parseMsg( $error ); $this->dieUsage( $parsed['info'], $parsed['code'] ); } @@ -1091,6 +1212,14 @@ abstract class ApiBase { */ public function parseMsg( $error ) { $key = array_shift( $error ); + + // Check whether the error array was nested + // array( array( , ), array( , ) ) + if( is_array( $key ) ){ + $error = $key; + $key = array_shift( $error ); + } + if ( isset( self::$messageMap[$key] ) ) { return array( 'code' => wfMsgReplaceArgs( self::$messageMap[$key]['code'], $error ), @@ -1098,6 +1227,7 @@ abstract class ApiBase { wfMsgReplaceArgs( self::$messageMap[$key]['info'], $error ) ); } + // If the key isn't present, throw an "unknown error" return $this->parseMsg( array( 'unknownerror', $key ) ); } @@ -1144,7 +1274,7 @@ abstract class ApiBase { /** * Returns whether this module requires a Token to execute - * @returns bool + * @return bool */ public function needsToken() { return false; @@ -1152,17 +1282,18 @@ abstract class ApiBase { /** * Returns the token salt if there is one, '' if the module doesn't require a salt, else false if the module doesn't need a token - * @returns bool + * @return bool */ public function getTokenSalt() { return false; } /** - * Gets the user for whom to get the watchlist - * - * @returns User - */ + * Gets the user for whom to get the watchlist + * + * @param $params array + * @return User + */ public function getWatchlistUser( $params ) { global $wgUser; if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) { @@ -1183,6 +1314,13 @@ abstract class ApiBase { return $user; } + /** + * @return false|string|array Returns a false if the module has no help url, else returns a (array of) string + */ + public function getHelpUrls() { + return false; + } + /** * Returns a list of all possible errors returned by the module * @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... ) @@ -1363,6 +1501,6 @@ abstract class ApiBase { * @return string */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiBase.php 82730 2011-02-24 16:03:05Z reedy $'; + return __CLASS__ . ': $Id: ApiBase.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index 875b8aeb..8d718ab2 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -37,9 +37,6 @@ if ( !defined( 'MEDIAWIKI' ) ) { */ class ApiBlock extends ApiBase { - /** - * Std ctor. - */ public function __construct( $main, $action ) { parent::__construct( $main, $action ); } @@ -51,56 +48,71 @@ class ApiBlock extends ApiBase { * of success. If it fails, the result will specify the nature of the error. */ public function execute() { - global $wgUser, $wgBlockAllowsUTEdit; + global $wgUser; $params = $this->extractRequestParams(); if ( $params['gettoken'] ) { - $res['blocktoken'] = $wgUser->editToken(); + $res['blocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() ); $this->getResult()->addValue( null, $this->getModuleName(), $res ); return; } if ( !$wgUser->isAllowed( 'block' ) ) { - $this->dieUsageMsg( array( 'cantblock' ) ); + $this->dieUsageMsg( 'cantblock' ); } # bug 15810: blocked admins should have limited access here if ( $wgUser->isBlocked() ) { - $status = IPBlockForm::checkUnblockSelf( $params['user'] ); + $status = SpecialBlock::checkUnblockSelf( $params['user'] ); if ( $status !== true ) { $this->dieUsageMsg( array( $status ) ); } } if ( $params['hidename'] && !$wgUser->isAllowed( 'hideuser' ) ) { - $this->dieUsageMsg( array( 'canthide' ) ); + $this->dieUsageMsg( 'canthide' ); } - if ( $params['noemail'] && !IPBlockForm::canBlockEmail( $wgUser ) ) { - $this->dieUsageMsg( array( 'cantblock-email' ) ); + if ( $params['noemail'] && !SpecialBlock::canBlockEmail( $wgUser ) ) { + $this->dieUsageMsg( 'cantblock-email' ); } - $form = new IPBlockForm( '' ); - $form->BlockAddress = $params['user']; - $form->BlockReason = ( is_null( $params['reason'] ) ? '' : $params['reason'] ); - $form->BlockReasonList = 'other'; - $form->BlockExpiry = ( $params['expiry'] == 'never' ? 'infinite' : $params['expiry'] ); - $form->BlockOther = ''; - $form->BlockAnonOnly = $params['anononly']; - $form->BlockCreateAccount = $params['nocreate']; - $form->BlockEnableAutoblock = $params['autoblock']; - $form->BlockEmail = $params['noemail']; - $form->BlockHideName = $params['hidename']; - $form->BlockAllowUsertalk = $params['allowusertalk'] && $wgBlockAllowsUTEdit; - $form->BlockReblock = $params['reblock']; - - $userID = $expiry = null; - $retval = $form->doBlock( $userID, $expiry ); - if ( count( $retval ) ) { + $data = array( + 'Target' => $params['user'], + 'Reason' => array( + is_null( $params['reason'] ) ? '' : $params['reason'], + 'other', + is_null( $params['reason'] ) ? '' : $params['reason'] + ), + 'Expiry' => $params['expiry'] == 'never' ? 'infinite' : $params['expiry'], + 'HardBlock' => !$params['anononly'], + 'CreateAccount' => $params['nocreate'], + 'AutoBlock' => $params['autoblock'], + 'DisableEmail' => $params['noemail'], + 'HideUser' => $params['hidename'], + 'DisableUTEdit' => $params['allowusertalk'], + 'AlreadyBlocked' => $params['reblock'], + 'Watch' => $params['watchuser'], + 'Confirm' => true, + ); + + $retval = SpecialBlock::processForm( $data ); + if ( $retval !== true ) { // We don't care about multiple errors, just report one of them $this->dieUsageMsg( $retval ); } + list( $target, /*...*/ ) = SpecialBlock::getTargetAndType( $params['user'] ); $res['user'] = $params['user']; - $res['userID'] = intval( $userID ); - $res['expiry'] = ( $expiry == Block::infinity() ? 'infinite' : wfTimestamp( TS_ISO_8601, $expiry ) ); + $res['userID'] = $target instanceof User ? $target->getId() : 0; + + $block = Block::newFromTarget( $target ); + if( $block instanceof Block ){ + $res['expiry'] = $block->mExpiry == wfGetDB( DB_SLAVE )->getInfinity() + ? 'infinite' + : wfTimestamp( TS_ISO_8601, $block->mExpiry ); + } else { + # should be unreachable + $res['expiry'] = ''; + } + $res['reason'] = $params['reason']; if ( $params['anononly'] ) { $res['anononly'] = ''; @@ -120,6 +132,9 @@ class ApiBlock extends ApiBase { if ( $params['allowusertalk'] ) { $res['allowusertalk'] = ''; } + if ( $params['watchuser'] ) { + $res['watchuser'] = ''; + } $this->getResult()->addValue( null, $this->getModuleName(), $res ); } @@ -149,6 +164,7 @@ class ApiBlock extends ApiBase { 'hidename' => false, 'allowusertalk' => false, 'reblock' => false, + 'watchuser' => false, ); } @@ -166,6 +182,7 @@ class ApiBlock extends ApiBase { 'hidename' => 'Hide the username from the block log. (Requires the "hideuser" right.)', 'allowusertalk' => 'Allow the user to edit their own talk page (depends on $wgBlockAllowsUTEdit)', 'reblock' => 'If the user is already blocked, overwrite the existing block', + 'watchuser' => 'Watch the user/IP\'s user and talk pages', ); } @@ -198,7 +215,11 @@ class ApiBlock extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Block'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiBlock.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiBlock.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php new file mode 100644 index 00000000..d43fa53f --- /dev/null +++ b/includes/api/ApiComparePages.php @@ -0,0 +1,130 @@ +extractRequestParams(); + + $rev1 = $this->revisionOrTitle( $params['fromrev'], $params['fromtitle'] ); + $rev2 = $this->revisionOrTitle( $params['torev'], $params['totitle'] ); + + $de = new DifferenceEngine( null, + $rev1, + $rev2, + null, // rcid + true, + false ); + + $vals = array(); + if ( isset( $params['fromtitle'] ) ) { + $vals['fromtitle'] = $params['fromtitle']; + } + $vals['fromrevid'] = $rev1; + if ( isset( $params['totitle'] ) ) { + $vals['totitle'] = $params['totitle']; + } + $vals['torevid'] = $rev2; + + $difftext = $de->getDiffBody(); + + if ( $difftext === false ) { + $this->dieUsage( 'The diff cannot be retrieved. ' . + 'Maybe one or both revisions do not exist or you do not have permission to view them.', 'baddiff' ); + } else { + ApiResult::setContent( $vals, $difftext ); + } + + $this->getResult()->addValue( null, $this->getModuleName(), $vals ); + } + + /** + * @param $revision int + * @param $titleText string + * @return int + */ + private function revisionOrTitle( $revision, $titleText ) { + if( $revision ){ + return $revision; + } elseif( $titleText ) { + $title = Title::newFromText( $titleText ); + if( !$title ){ + $this->dieUsageMsg( array( 'invalidtitle', $titleText ) ); + } + return $title->getLatestRevID(); + } + $this->dieUsage( 'inputneeded', 'A title or a revision number is needed for both the from and the to parameters' ); + } + + public function getAllowedParams() { + return array( + 'fromtitle' => null, + 'fromrev' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + 'totitle' => null, + 'torev' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + ); + } + + public function getParamDescription() { + return array( + 'fromtitle' => 'First title to compare', + 'fromrev' => 'First revision to compare', + 'totitle' => 'Second title to compare', + 'torev' => 'Second revision to compare', + ); + } + public function getDescription() { + return array( + 'Get the difference between 2 pages', + 'You must pass a revision number or a page title for each part (1 and 2)' + ); + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'code' => 'inputneeded', 'info' => 'A title or a revision is needed' ), + array( 'invalidtitle', 'title' ), + array( 'code' => 'baddiff', 'info' => 'The diff cannot be retrieved. Maybe one or both revisions do not exist or you do not have permission to view them.' ), + ) ); + } + + protected function getExamples() { + return array( + 'api.php?action=compare&fromrev=1&torev=2', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiComparePages.php 92400 2011-07-17 16:51:11Z reedy $'; + } +} diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index fbf62391..58befbfe 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -65,7 +65,7 @@ class ApiDelete extends ApiBase { } } if ( !$titleObj->exists() ) { - $this->dieUsageMsg( array( 'notanarticle' ) ); + $this->dieUsageMsg( 'notanarticle' ); } $reason = ( isset( $params['reason'] ) ? $params['reason'] : null ); @@ -146,22 +146,17 @@ class ApiDelete extends ApiBase { } $error = ''; - if ( !wfRunHooks( 'ArticleDelete', array( &$article, &$wgUser, &$reason, &$error ) ) ) { - return array( array( 'hookaborted', $error ) ); - } - // Luckily, Article.php provides a reusable delete function that does the hard work for us - if ( $article->doDeleteArticle( $reason ) ) { - wfRunHooks( 'ArticleDeleteComplete', array( &$article, &$wgUser, $reason, $article->getId() ) ); + if ( $article->doDeleteArticle( $reason, false, 0, true, $error ) ) { return array(); + } else { + return array( array( 'cannotdelete', $article->getTitle()->getPrefixedText() ) ); } - return array( array( 'cannotdelete', $article->mTitle->getPrefixedText() ) ); } /** - * @static * @param $token - * @param $title + * @param $title Title * @param $oldimage * @param $reason * @param $suppress bool @@ -255,12 +250,15 @@ class ApiDelete extends ApiBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'invalidtitle', 'title' ), - array( 'nosuchpageid', 'pageid' ), - array( 'notanarticle' ), - array( 'hookaborted', 'error' ), - ) ); + return array_merge( parent::getPossibleErrors(), + $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ), + array( + array( 'invalidtitle', 'title' ), + array( 'nosuchpageid', 'pageid' ), + array( 'notanarticle' ), + array( 'hookaborted', 'error' ), + ) + ); } public function needsToken() { @@ -278,7 +276,11 @@ class ApiDelete extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Delete'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiDelete.php 77141 2010-11-23 10:04:38Z ialex $'; + return __CLASS__ . ': $Id: ApiDelete.php 104449 2011-11-28 15:52:04Z reedy $'; } -} \ No newline at end of file +} diff --git a/includes/api/ApiDisabled.php b/includes/api/ApiDisabled.php index f83bfdc9..947267f3 100644 --- a/includes/api/ApiDisabled.php +++ b/includes/api/ApiDisabled.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,6 +70,6 @@ class ApiDisabled extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiDisabled.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiDisabled.php 79969 2011-01-10 22:36:26Z reedy $'; } } diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index 75cc0ba2..ffc82640 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -1,6 +1,6 @@ dieUsageMsg( array( 'missingtext' ) ); + $this->dieUsageMsg( 'missingtext' ); } $titleObj = Title::newFromText( $params['title'] ); if ( !$titleObj || $titleObj->isExternal() ) { $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); } - - if( $params['redirect'] ) { - if( $titleObj->isRedirect() ) { + + $apiResult = $this->getResult(); + + if ( $params['redirect'] ) { + if ( $titleObj->isRedirect() ) { $oldTitle = $titleObj; - + $titles = Title::newFromRedirectArray( Revision::newFromTitle( $oldTitle )->getText( Revision::FOR_THIS_USER ) ); + // array_shift( $titles ); $redirValues = array(); foreach ( $titles as $id => $newTitle ) { - - if( !isset( $titles[ $id - 1 ] ) ) { + + if ( !isset( $titles[ $id - 1 ] ) ) { $titles[ $id - 1 ] = $oldTitle; } - + $redirValues[] = array( 'from' => $titles[ $id - 1 ]->getPrefixedText(), 'to' => $newTitle->getPrefixedText() ); - + $titleObj = $newTitle; } - - $this->getResult()->setIndexedTagName( $redirValues, 'r' ); - $this->getResult()->addValue( null, 'redirects', $redirValues ); + $apiResult->setIndexedTagName( $redirValues, 'r' ); + $apiResult->addValue( null, 'redirects', $redirValues ); } } @@ -90,10 +92,10 @@ class ApiEditPage extends ApiBase { $wgTitle = $titleObj; if ( $params['createonly'] && $titleObj->exists() ) { - $this->dieUsageMsg( array( 'createonly-exists' ) ); + $this->dieUsageMsg( 'createonly-exists' ); } if ( $params['nocreate'] && !$titleObj->exists() ) { - $this->dieUsageMsg( array( 'nocreate-missing' ) ); + $this->dieUsageMsg( 'nocreate-missing' ); } // Now let's check whether we're even allowed to do this @@ -161,7 +163,7 @@ class ApiEditPage extends ApiBase { $newtext = $articleObj->getUndoText( $undoRev, $undoafterRev ); if ( $newtext === false ) { - $this->dieUsageMsg( array( 'undo-failure' ) ); + $this->dieUsageMsg( 'undo-failure' ); } $params['text'] = $newtext; // If no summary was given and we only undid one rev, @@ -173,10 +175,12 @@ class ApiEditPage extends ApiBase { // See if the MD5 hash checks out if ( !is_null( $params['md5'] ) && md5( $toMD5 ) !== $params['md5'] ) { - $this->dieUsageMsg( array( 'hashcheckfailed' ) ); + $this->dieUsageMsg( 'hashcheckfailed' ); } $ep = new EditPage( $articleObj ); + $ep->setContextTitle( $titleObj ); + // EditPage wants to parse its stuff from a WebRequest // That interface kind of sucks, but it's workable $reqArr = array( @@ -251,10 +255,10 @@ class ApiEditPage extends ApiBase { if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) { if ( count( $r ) ) { $r['result'] = 'Failure'; - $this->getResult()->addValue( null, $this->getModuleName(), $r ); + $apiResult->addValue( null, $this->getModuleName(), $r ); return; } else { - $this->dieUsageMsg( array( 'hookaborted' ) ); + $this->dieUsageMsg( 'hookaborted' ); } } @@ -262,65 +266,65 @@ class ApiEditPage extends ApiBase { $oldRevId = $articleObj->getRevIdFetched(); $result = null; // Fake $wgRequest for some hooks inside EditPage - // FIXME: This interface SUCKS + // @todo FIXME: This interface SUCKS $oldRequest = $wgRequest; $wgRequest = $req; - $retval = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] ); + $status = $ep->internalAttemptSave( $result, $wgUser->isAllowed( 'bot' ) && $params['bot'] ); $wgRequest = $oldRequest; global $wgMaxArticleSize; - switch( $retval ) { + switch( $status->value ) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: - $this->dieUsageMsg( array( 'hookaborted' ) ); + $this->dieUsageMsg( 'hookaborted' ); case EditPage::AS_IMAGE_REDIRECT_ANON: - $this->dieUsageMsg( array( 'noimageredirect-anon' ) ); + $this->dieUsageMsg( 'noimageredirect-anon' ); case EditPage::AS_IMAGE_REDIRECT_LOGGED: - $this->dieUsageMsg( array( 'noimageredirect-logged' ) ); + $this->dieUsageMsg( 'noimageredirect-logged' ); case EditPage::AS_SPAM_ERROR: $this->dieUsageMsg( array( 'spamdetected', $result['spam'] ) ); case EditPage::AS_FILTERING: - $this->dieUsageMsg( array( 'filtered' ) ); + $this->dieUsageMsg( 'filtered' ); case EditPage::AS_BLOCKED_PAGE_FOR_USER: - $this->dieUsageMsg( array( 'blockedtext' ) ); + $this->dieUsageMsg( 'blockedtext' ); case EditPage::AS_MAX_ARTICLE_SIZE_EXCEEDED: case EditPage::AS_CONTENT_TOO_BIG: $this->dieUsageMsg( array( 'contenttoobig', $wgMaxArticleSize ) ); case EditPage::AS_READ_ONLY_PAGE_ANON: - $this->dieUsageMsg( array( 'noedit-anon' ) ); + $this->dieUsageMsg( 'noedit-anon' ); case EditPage::AS_READ_ONLY_PAGE_LOGGED: - $this->dieUsageMsg( array( 'noedit' ) ); + $this->dieUsageMsg( 'noedit' ); case EditPage::AS_READ_ONLY_PAGE: $this->dieReadOnly(); case EditPage::AS_RATE_LIMITED: - $this->dieUsageMsg( array( 'actionthrottledtext' ) ); + $this->dieUsageMsg( 'actionthrottledtext' ); case EditPage::AS_ARTICLE_WAS_DELETED: - $this->dieUsageMsg( array( 'wasdeleted' ) ); + $this->dieUsageMsg( 'wasdeleted' ); case EditPage::AS_NO_CREATE_PERMISSION: - $this->dieUsageMsg( array( 'nocreate-loggedin' ) ); + $this->dieUsageMsg( 'nocreate-loggedin' ); case EditPage::AS_BLANK_ARTICLE: - $this->dieUsageMsg( array( 'blankpage' ) ); + $this->dieUsageMsg( 'blankpage' ); case EditPage::AS_CONFLICT_DETECTED: - $this->dieUsageMsg( array( 'editconflict' ) ); + $this->dieUsageMsg( 'editconflict' ); // case EditPage::AS_SUMMARY_NEEDED: Can't happen since we set wpIgnoreBlankSummary case EditPage::AS_TEXTBOX_EMPTY: - $this->dieUsageMsg( array( 'emptynewsection' ) ); + $this->dieUsageMsg( 'emptynewsection' ); case EditPage::AS_SUCCESS_NEW_ARTICLE: $r['new'] = ''; @@ -346,20 +350,17 @@ class ApiEditPage extends ApiBase { break; case EditPage::AS_SUMMARY_NEEDED: - $this->dieUsageMsg( array( 'summaryrequired' ) ); + $this->dieUsageMsg( 'summaryrequired' ); case EditPage::AS_END: - // This usually means some kind of race condition - // or DB weirdness occurred. Fall through to throw an unknown - // error. - - // This needs fixing higher up, as Article::doEdit should be - // used rather than Article::updateArticle, so that specific - // error conditions can be returned + // $status came from WikiPage::doEdit() + $errors = $status->getErrorsArray(); + $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map + break; default: - $this->dieUsageMsg( array( 'unknownerror', $retval ) ); + $this->dieUsageMsg( array( 'unknownerror', $status->value ) ); } - $this->getResult()->addValue( null, $this->getModuleName(), $r ); + $apiResult->addValue( null, $this->getModuleName(), $r ); } public function mustBePosted() { @@ -406,6 +407,8 @@ class ApiEditPage extends ApiBase { array( 'unknownerror', 'retval' ), array( 'code' => 'nosuchsection', 'info' => 'There is no section section.' ), array( 'code' => 'invalidsection', 'info' => 'The section parameter must be set to an integer or \'new\'' ), + array( 'customcssprotected' ), + array( 'customjsprotected' ), ) ); } @@ -468,12 +471,14 @@ class ApiEditPage extends ApiBase { 'title' => 'Page title', 'section' => 'Section number. 0 for the top section, \'new\' for a new section', 'text' => 'Page content', - 'token' => 'Edit token. You can get one of these through prop=info', + 'token' => array( 'Edit token. You can get one of these through prop=info.', + 'The token should always be sent as the last parameter, or at least, after the text parameter' + ), 'summary' => 'Edit summary. Also section title when section=new', 'minor' => 'Minor edit', 'notminor' => 'Non-minor edit', 'bot' => 'Mark this edit as bot', - 'basetimestamp' => array( 'Timestamp of the base revision (gotten through prop=revisions&rvprop=timestamp).', + 'basetimestamp' => array( 'Timestamp of the base revision (obtained through prop=revisions&rvprop=timestamp).', 'Used to detect edit conflicts; leave unset to ignore conflicts.' ), 'starttimestamp' => array( 'Timestamp when you obtained the edit token.', @@ -516,7 +521,11 @@ class ApiEditPage extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Edit'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiEditPage.php 90492 2011-06-20 22:39:10Z reedy $'; + return __CLASS__ . ': $Id: ApiEditPage.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiEmailUser.php b/includes/api/ApiEmailUser.php index ab58eb18..9ce43183 100644 --- a/includes/api/ApiEmailUser.php +++ b/includes/api/ApiEmailUser.php @@ -1,6 +1,6 @@ setRemoveComments( false ); + } + if ( $params['generatexml'] ) { $wgParser->startExternalParse( $title_obj, $options, OT_PREPROCESS ); $dom = $wgParser->preprocessToDom( $params['text'] ); @@ -86,8 +90,12 @@ class ApiExpandTemplates extends ApiBase { 'title' => array( ApiBase::PARAM_DFLT => 'API', ), - 'text' => null, + 'text' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ), 'generatexml' => false, + 'includecomments' => false, ); } @@ -96,11 +104,12 @@ class ApiExpandTemplates extends ApiBase { 'text' => 'Wikitext to convert', 'title' => 'Title of page', 'generatexml' => 'Generate XML parse tree', + 'includecomments' => 'Whether to include HTML comments in the output', ); } public function getDescription() { - return 'This module expand all templates in wikitext'; + return 'Expands all templates in wikitext'; } protected function getExamples() { @@ -109,7 +118,11 @@ class ApiExpandTemplates extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#expandtemplates'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiExpandTemplates.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiExpandTemplates.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php new file mode 100644 index 00000000..c06b71af --- /dev/null +++ b/includes/api/ApiFeedContributions.php @@ -0,0 +1,207 @@ +getMain() ); + } + + public function execute() { + $params = $this->extractRequestParams(); + + global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode; + + if( !$wgFeed ) { + $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' ); + } + + if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) { + $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' ); + } + + global $wgMiserMode; + if ( $params['showsizediff'] && $wgMiserMode ) { + $this->dieUsage( 'Size difference is disabled in Miser Mode', 'sizediffdisabled' ); + } + + $msg = wfMsgForContent( 'Contributions' ); + $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']'; + $feedUrl = SpecialPage::getTitleFor( 'Contributions', $params['user'] )->getFullURL(); + + $target = $params['user'] == 'newbies' + ? 'newbies' + : Title::makeTitleSafe( NS_USER, $params['user'] )->getText(); + + $feed = new $wgFeedClasses[$params['feedformat']] ( + $feedTitle, + htmlspecialchars( $msg ), + $feedUrl + ); + + $pager = new ContribsPager( array( + 'target' => $target, + 'namespace' => $params['namespace'], + 'year' => $params['year'], + 'month' => $params['month'], + 'tagFilter' => $params['tagfilter'], + 'deletedOnly' => $params['deletedonly'], + 'topOnly' => $params['toponly'], + 'showSizeDiff' => $params['showsizediff'], + ) ); + + $feedItems = array(); + if( $pager->getNumRows() > 0 ) { + foreach ( $pager->mResult as $row ) { + $feedItems[] = $this->feedItem( $row ); + } + } + + ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems ); + } + + protected function feedItem( $row ) { + $title = Title::MakeTitle( intval( $row->page_namespace ), $row->page_title ); + if( $title ) { + $date = $row->rev_timestamp; + $comments = $title->getTalkPage()->getFullURL(); + $revision = Revision::newFromRow( $row); + + return new FeedItem( + $title->getPrefixedText(), + $this->feedItemDesc( $revision ), + $title->getFullURL(), + $date, + $this->feedItemAuthor( $revision ), + $comments + ); + } else { + return null; + } + } + + /** + * @param $revision Revision + * @return string + */ + protected function feedItemAuthor( $revision ) { + return $revision->getUserText(); + } + + /** + * @param $revision Revision + * @return string + */ + protected function feedItemDesc( $revision ) { + if( $revision ) { + return '

' . htmlspecialchars( $revision->getUserText() ) . wfMsgForContent( 'colon-separator' ) . + htmlspecialchars( FeedItem::stripComment( $revision->getComment() ) ) . + "

\n
\n
" . + nl2br( htmlspecialchars( $revision->getText() ) ) . "
"; + } + return ''; + } + + public function getAllowedParams() { + global $wgFeedClasses; + $feedFormatNames = array_keys( $wgFeedClasses ); + return array ( + 'feedformat' => array( + ApiBase::PARAM_DFLT => 'rss', + ApiBase::PARAM_TYPE => $feedFormatNames + ), + 'user' => array( + ApiBase::PARAM_TYPE => 'user', + ApiBase::PARAM_REQUIRED => true, + ), + 'namespace' => array( + ApiBase::PARAM_TYPE => 'namespace', + ApiBase::PARAM_ISMULTI => true + ), + 'year' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + 'month' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + 'tagfilter' => array( + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => array_values( ChangeTags::listDefinedTags() ), + ApiBase::PARAM_DFLT => '', + ), + 'deletedonly' => false, + 'toponly' => false, + 'showsizediff' => false, + ); + } + + public function getParamDescription() { + return array( + 'feedformat' => 'The format of the feed', + 'user' => 'What users to get the contributions for', + 'namespace' => 'What namespace to filter the contributions by', + 'year' => 'From year (and earlier)', + 'month' => 'From month (and earlier)', + 'tagfilter' => 'Filter contributions that have these tags', + 'deletedonly' => 'Show only deleted contributions', + 'toponly' => 'Only show edits that are latest revisions', + 'showsizediff' => 'Show the size difference between revisions. Disabled in Miser Mode', + ); + } + + public function getDescription() { + return 'Returns a user contributions feed'; + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ), + array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ), + array( 'code' => 'sizediffdisabled', 'info' => 'Size difference is disabled in Miser Mode' ), + ) ); + } + + protected function getExamples() { + return array( + 'api.php?action=feedcontributions&user=Reedy', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFeedContributions.php 95607 2011-08-27 19:28:13Z hashar $'; + } +} \ No newline at end of file diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index e1ba61f6..75ce7ca0 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -1,6 +1,6 @@ extractRequestParams(); + if( !$wgFeed ) { + $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' ); + } + + if( !isset( $wgFeedClasses[ $params['feedformat'] ] ) ) { + $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' ); + } + // limit to the number of hours going from now back $endTime = wfTimestamp( TS_MW, time() - intval( $params['hours'] * 60 * 60 ) ); @@ -90,7 +98,7 @@ class ApiFeedWatchlist extends ApiBase { } // Check for 'allrev' parameter, and if found, show all revisions to each page on wl. - if ( !is_null( $params['allrev'] ) ) { + if ( $params['allrev'] ) { $fauxReqArr['wlallrev'] = ''; } @@ -109,10 +117,12 @@ class ApiFeedWatchlist extends ApiBase { $feedItems[] = $this->createFeedItem( $info ); } - $feedTitle = $wgSitename . ' - ' . wfMsgForContent( 'watchlist' ) . ' [' . $wgLanguageCode . ']'; + $msg = wfMsgForContent( 'watchlist' ); + + $feedTitle = $wgSitename . ' - ' . $msg . ' [' . $wgLanguageCode . ']'; $feedUrl = SpecialPage::getTitleFor( 'Watchlist' )->getFullURL(); - $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( wfMsgForContent( 'watchlist' ) ), $feedUrl ); + $feed = new $wgFeedClasses[$params['feedformat']] ( $feedTitle, htmlspecialchars( $msg ), $feedUrl ); ApiFormatFeedWrapper::setResult( $this->getResult(), $feed, $feedItems ); @@ -171,7 +181,7 @@ class ApiFeedWatchlist extends ApiBase { ApiBase::PARAM_MIN => 1, ApiBase::PARAM_MAX => 72, ), - 'allrev' => null, + 'allrev' => false, 'wlowner' => array( ApiBase::PARAM_TYPE => 'user' ), @@ -189,22 +199,33 @@ class ApiFeedWatchlist extends ApiBase { 'allrev' => 'Include multiple revisions of the same page within given timeframe', 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}token if it's not you)", 'wltoken' => 'Security token that requested user set in their preferences', - 'linktodiffs'=> 'Link to change differences instead of article pages' + 'linktodiffs' => 'Link to change differences instead of article pages' ); } public function getDescription() { - return 'This module returns a watchlist feed'; + return 'Returns a watchlist feed'; + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'code' => 'feed-unavailable', 'info' => 'Syndication feeds are not available' ), + array( 'code' => 'feed-invalid', 'info' => 'Invalid subscription feed type' ), + ) ); } protected function getExamples() { return array( 'api.php?action=feedwatchlist', - 'api.php?action=feedwatchlist&allrev=allrev&linktodiffs=&hours=6' + 'api.php?action=feedwatchlist&allrev=&linktodiffs=&hours=6' ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Watchlist_feed'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiFeedWatchlist.php 77674 2010-12-03 19:47:22Z catrope $'; + return __CLASS__ . ': $Id: ApiFeedWatchlist.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiFileRevert.php b/includes/api/ApiFileRevert.php new file mode 100644 index 00000000..1540fe6c --- /dev/null +++ b/includes/api/ApiFileRevert.php @@ -0,0 +1,189 @@ + + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +if ( !defined( 'MEDIAWIKI' ) ) { + // Eclipse helper - will be ignored in production + require_once( "ApiBase.php" ); +} + +/** + * @ingroup API + */ +class ApiFileRevert extends ApiBase { + + /** + * @var File + */ + protected $file; + protected $archiveName; + + protected $params; + + public function __construct( $main, $action ) { + parent::__construct( $main, $action ); + } + + public function execute() { + global $wgUser; + + $this->params = $this->extractRequestParams(); + // Extract the file and archiveName from the request parameters + $this->validateParameters(); + + // Check whether we're allowed to revert this file + $this->checkPermissions( $wgUser ); + + $sourceUrl = $this->file->getArchiveVirtualUrl( $this->archiveName ); + $status = $this->file->upload( $sourceUrl, $this->params['comment'], $this->params['comment'] ); + + if ( $status->isGood() ) { + $result = array( 'result' => 'Success' ); + } else { + $result = array( + 'result' => 'Failure', + 'errors' => $this->getResult()->convertStatusToArray( $status ), + ); + } + + $this->getResult()->addValue( null, $this->getModuleName(), $result ); + + } + + /** + * Checks that the user has permissions to perform this revert. + * Dies with usage message on inadequate permissions. + * @param $user User The user to check. + */ + protected function checkPermissions( $user ) { + $permissionErrors = array_merge( + $this->file->getTitle()->getUserPermissionsErrors( 'edit' , $user ), + $this->file->getTitle()->getUserPermissionsErrors( 'upload' , $user ) + ); + + if ( $permissionErrors ) { + $this->dieUsageMsg( $permissionErrors[0] ); + } + } + + /** + * Validate the user parameters and set $this->archiveName and $this->file. + * Throws an error if validation fails + */ + protected function validateParameters() { + // Validate the input title + $title = Title::makeTitleSafe( NS_FILE, $this->params['filename'] ); + if ( is_null( $title ) ) { + $this->dieUsageMsg( array( 'invalidtitle', $this->params['filename'] ) ); + } + // Check if the file really exists + $this->file = wfLocalFile( $title ); + if ( !$this->file->exists() ) { + $this->dieUsageMsg( 'notanarticle' ); + } + + // Check if the archivename is valid for this file + $this->archiveName = $this->params['archivename']; + $oldFile = RepoGroup::singleton()->getLocalRepo()->newFromArchiveName( $title, $this->archiveName ); + if ( !$oldFile->exists() ) { + $this->dieUsageMsg( 'filerevert-badversion' ); + } + } + + public function mustBePosted() { + return true; + } + + public function isWriteMode() { + return true; + } + + public function getAllowedParams() { + return array( + 'filename' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ), + 'comment' => array( + ApiBase::PARAM_DFLT => '', + ), + 'archivename' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_REQUIRED => true, + ), + 'token' => null, + ); + + } + + public function getParamDescription() { + $params = array( + 'filename' => 'Target filename', + 'token' => 'Edit token. You can get one of these through prop=info', + 'comment' => 'Upload comment', + 'archivename' => 'Archive name of the revision to revert to', + ); + + return $params; + + } + + public function getDescription() { + return array( + 'Revert a file to an old version' + ); + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), + array( + array( 'mustbeloggedin', 'upload' ), + array( 'badaccess-groups' ), + array( 'invalidtitle', 'title' ), + array( 'notanarticle' ), + array( 'filerevert-badversion' ), + ) + ); + } + + public function needsToken() { + return true; + } + + public function getTokenSalt() { + return ''; + } + + protected function getExamples() { + return array( + 'Revert Wiki.png to the version of 20110305152740:', + ' api.php?action=filerevert&filename=Wiki.png&comment=Revert&archivename=20110305152740!Wiki.png&token=+\\', + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiFileRevert.php 92400 2011-07-17 16:51:11Z reedy $'; + } +} diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index 9d1dfbc1..ce881599 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -1,6 +1,6 @@ getMain()->getRequest()->response()->header( "Content-Type: $mime; charset=utf-8" ); if ( $isHtml ) { ?> - + mUnescapeAmps ) { @@ -169,7 +169,7 @@ abstract class ApiFormatBase extends ApiBase { You are looking at the HTML representation of the mFormat ); ?> format.
HTML is good for debugging, but probably is not suitable for your application.
-See complete documentation, or +See complete documentation, or API help for more information.
complete documentation, or * @return string */ protected function formatHTML( $text ) { - global $wgUrlProtocols; - // Escape everything first for full coverage $text = htmlspecialchars( $text ); // encode all comments or tags as safe blue strings $text = preg_replace( '/\<(!--.*?--|.*?)\>/', '<\1>', $text ); // identify URLs - $protos = implode( "|", $wgUrlProtocols ); + $protos = wfUrlProtocolsWithoutProtRel(); // This regex hacks around bug 13218 (" included in the URL) $text = preg_replace( "#(($protos).*?)(")?([ \\'\"<>\n]|<|>|")#", '\\1\\3\\4', $text ); // identify requests to api.php @@ -294,12 +292,16 @@ See complete documentation, or return 'api.php?action=query&meta=siteinfo&siprop=namespaces&format=' . $this->getModuleName(); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Data_formats'; + } + public function getDescription() { return $this->getIsHtml() ? ' (pretty-print in HTML)' : ''; } public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 104449 2011-11-28 15:52:04Z reedy $'; } } @@ -368,6 +370,6 @@ class ApiFormatFeedWrapper extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatBase.php 75970 2010-11-04 00:55:30Z reedy $'; + return __CLASS__ . ': $Id: ApiFormatBase.php 104449 2011-11-28 15:52:04Z reedy $'; } -} \ No newline at end of file +} diff --git a/includes/api/ApiFormatDbg.php b/includes/api/ApiFormatDbg.php index d4aeb0b8..00b03494 100644 --- a/includes/api/ApiFormatDbg.php +++ b/includes/api/ApiFormatDbg.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,6 +55,6 @@ class ApiFormatDbg extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatDbg.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatDbg.php 78829 2010-12-22 20:52:06Z reedy $'; } } diff --git a/includes/api/ApiFormatDump.php b/includes/api/ApiFormatDump.php index 6197563d..b88572e9 100644 --- a/includes/api/ApiFormatDump.php +++ b/includes/api/ApiFormatDump.php @@ -1,6 +1,6 @@ .@home.nl + * Copyright © 2009 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,7 +38,7 @@ class ApiFormatRaw extends ApiFormatBase { /** * Constructor * @param $main ApiMain object - * @param $errorFallback Formatter object to fall back on for errors + * @param $errorFallback ApiFormatBase object to fall back on for errors */ public function __construct( $main, $errorFallback ) { parent::__construct( $main, 'raw' ); @@ -73,6 +73,6 @@ class ApiFormatRaw extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatRaw.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatRaw.php 82429 2011-02-19 00:30:18Z reedy $'; } } diff --git a/includes/api/ApiFormatTxt.php b/includes/api/ApiFormatTxt.php index bbb268f1..bb5a3ba1 100644 --- a/includes/api/ApiFormatTxt.php +++ b/includes/api/ApiFormatTxt.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -55,6 +55,6 @@ class ApiFormatTxt extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatTxt.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatTxt.php 78829 2010-12-22 20:52:06Z reedy $'; } } diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index 6c1e3066..2598cc55 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -1,6 +1,6 @@ extractRequestParams(); $this->mDoubleQuote = $params['xmldoublequote']; + $this->mIncludeNamespace = $params['includexmlnamespace']; $this->mXslt = $params['xslt']; $this->printText( '' ); if ( !is_null( $this->mXslt ) ) { $this->addXslt(); } + if ( $this->mIncludeNamespace ) { + $data = array( 'xmlns' => self::$namespace ) + $this->getResultData(); + } else { + $data = $this->getResultData(); + } + $this->printText( self::recXmlPrint( $this->mRootElemName, - $this->getResultData(), + $data, $this->getIsHtml() ? - 2 : null, $this->mDoubleQuote ) @@ -85,6 +94,13 @@ class ApiFormatXml extends ApiFormatBase { * * If neither key is found, all keys become element names, and values become element content. * The method is recursive, so the same rules apply to any sub-arrays. + * + * @param $elemName + * @param $elemValue + * @param $indent + * @param $doublequote bool + * + * @return string */ public static function recXmlPrint( $elemName, $elemValue, $indent, $doublequote = false ) { $retval = ''; @@ -193,6 +209,7 @@ class ApiFormatXml extends ApiFormatBase { return array( 'xmldoublequote' => false, 'xslt' => null, + 'includexmlnamespace' => false, ); } @@ -200,6 +217,7 @@ class ApiFormatXml extends ApiFormatBase { return array( 'xmldoublequote' => 'If specified, double quotes all attributes and content', 'xslt' => 'If specified, adds as stylesheet', + 'includexmlnamespace' => 'If specified, adds an XML namespace' ); } @@ -208,6 +226,6 @@ class ApiFormatXml extends ApiFormatBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatXml.php 73753 2010-09-25 16:56:03Z reedy $'; + return __CLASS__ . ': $Id: ApiFormatXml.php 104476 2011-11-28 20:08:17Z reedy $'; } } diff --git a/includes/api/ApiFormatYaml.php b/includes/api/ApiFormatYaml.php index ccf52746..d62bbbba 100644 --- a/includes/api/ApiFormatYaml.php +++ b/includes/api/ApiFormatYaml.php @@ -1,6 +1,6 @@ printText( Spyc::YAMLDump( $this->getResultData() ) ); - } - public function getDescription() { return 'Output data in YAML format' . parent::getDescription(); } public function getVersion() { - return __CLASS__ . ': $Id: ApiFormatYaml.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiFormatYaml.php 86302 2011-04-18 11:42:44Z reedy $'; } } diff --git a/includes/api/ApiHelp.php b/includes/api/ApiHelp.php index eedbde13..4b2ced7e 100644 --- a/includes/api/ApiHelp.php +++ b/includes/api/ApiHelp.php @@ -1,6 +1,6 @@ addValue( null, $this->getModuleName(), $r ); } + /** + * @param $module ApiBase + * @param $type String What type of request is this? e.g. action, query, list, prop, meta, format + * @return string + */ private function buildModuleHelp( $module, $type ) { $msg = ApiMain::makeHelpMsgHeader( $module, $type ); @@ -149,7 +154,15 @@ class ApiHelp extends ApiBase { ); } + public function getHelpUrls() { + return array( + 'https://www.mediawiki.org/wiki/API:Main_page', + 'https://www.mediawiki.org/wiki/API:FAQ', + 'https://www.mediawiki.org/wiki/API:Quick_start_guide', + ); + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiHelp.php 73863 2010-09-28 02:33:43Z brion $'; + return __CLASS__ . ': $Id: ApiHelp.php 104439 2011-11-28 15:22:23Z reedy $'; } } diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php index 1b5153f9..a1e5709a 100644 --- a/includes/api/ApiImport.php +++ b/includes/api/ApiImport.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2009 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -42,13 +42,14 @@ class ApiImport extends ApiBase { public function execute() { global $wgUser; - if ( !$wgUser->isAllowed( 'import' ) ) { - $this->dieUsageMsg( array( 'cantimport' ) ); - } + $params = $this->extractRequestParams(); $isUpload = false; if ( isset( $params['interwikisource'] ) ) { + if ( !$wgUser->isAllowed( 'import' ) ) { + $this->dieUsageMsg( 'cantimport' ); + } if ( !isset( $params['interwikipage'] ) ) { $this->dieUsageMsg( array( 'missingparam', 'interwikipage' ) ); } @@ -61,7 +62,7 @@ class ApiImport extends ApiBase { } else { $isUpload = true; if ( !$wgUser->isAllowed( 'importupload' ) ) { - $this->dieUsageMsg( array( 'cantimport-upload' ) ); + $this->dieUsageMsg( 'cantimport-upload' ); } $source = ImportStreamSource::newFromUpload( 'xml' ); } @@ -87,8 +88,9 @@ class ApiImport extends ApiBase { } $resultData = $reporter->getData(); - $this->getResult()->setIndexedTagName( $resultData, 'page' ); - $this->getResult()->addValue( null, $this->getModuleName(), $resultData ); + $result = $this->getResult(); + $result->setIndexedTagName( $resultData, 'page' ); + $result->addValue( null, $this->getModuleName(), $resultData ); } public function mustBePosted() { @@ -131,7 +133,11 @@ class ApiImport extends ApiBase { } public function getDescription() { - return 'Import a page from another wiki, or an XML file'; + return array( + 'Import a page from another wiki, or an XML file.' , + 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when', + 'sending a file for the "xml" parameter.' + ); } public function getPossibleErrors() { @@ -159,8 +165,12 @@ class ApiImport extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Import'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiImport.php 77800 2010-12-05 14:22:49Z ialex $'; + return __CLASS__ . ': $Id: ApiImport.php 104449 2011-11-28 15:52:04Z reedy $'; } } @@ -171,6 +181,14 @@ class ApiImport extends ApiBase { class ApiImportReporter extends ImportReporter { private $mResultArr = array(); + /** + * @param $title Title + * @param $origTitle Title + * @param $revisionCount int + * @param $successCount int + * @param $pageInfo + * @return void + */ function reportPage( $title, $origTitle, $revisionCount, $successCount, $pageInfo ) { // Add a result entry $r = array(); diff --git a/includes/api/ApiLogin.php b/includes/api/ApiLogin.php index 0675de7b..a09f0335 100644 --- a/includes/api/ApiLogin.php +++ b/includes/api/ApiLogin.php @@ -1,6 +1,6 @@ authenticateUserData() ) { + $authRes = $loginForm->authenticateUserData(); + switch ( $authRes ) { case LoginForm::SUCCESS: $wgUser->setOption( 'rememberpassword', 1 ); - $wgUser->setCookies(); + $wgUser->setCookies( $this->getMain()->getRequest() ); - // Run hooks. FIXME: split back and frontend from this hook. - // FIXME: This hook should be placed in the backend + // Run hooks. + // @todo FIXME: Split back and frontend from this hook. + // @todo FIXME: This hook should be placed in the backend $injected_html = ''; wfRunHooks( 'UserLoginComplete', array( &$wgUser, &$injected_html ) ); @@ -140,6 +142,11 @@ class ApiLogin extends ApiBase { $result['result'] = 'Blocked'; break; + case LoginForm::ABORTED: + $result['result'] = 'Aborted'; + $result['reason'] = $loginForm->mAbortLoginErrorMsg; + break; + default: ApiBase::dieDebug( __METHOD__, "Unhandled case value: {$authRes}" ); } @@ -175,7 +182,7 @@ class ApiLogin extends ApiBase { public function getDescription() { return array( - 'This module is used to login and get the authentication tokens. ', + 'Log in and get the authentication tokens. ', 'In the event of a successful log-in, a cookie will be attached', 'to your session. In the event of a failed log-in, you will not ', 'be able to attempt another log-in through this method for 5 seconds.', @@ -205,7 +212,11 @@ class ApiLogin extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Login'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiLogin.php 76080 2010-11-05 11:54:35Z catrope $'; + return __CLASS__ . ': $Id: ApiLogin.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiLogout.php b/includes/api/ApiLogout.php index 89326915..3639df3b 100644 --- a/includes/api/ApiLogout.php +++ b/includes/api/ApiLogout.php @@ -1,6 +1,6 @@ 'ApiExpandTemplates', 'parse' => 'ApiParse', 'opensearch' => 'ApiOpenSearch', + 'feedcontributions' => 'ApiFeedContributions', 'feedwatchlist' => 'ApiFeedWatchlist', 'help' => 'ApiHelp', 'paraminfo' => 'ApiParamInfo', 'rsd' => 'ApiRsd', + 'compare' => 'ApiComparePages', // Write modules 'purge' => 'ApiPurge', @@ -76,6 +78,7 @@ class ApiMain extends ApiBase { 'move' => 'ApiMove', 'edit' => 'ApiEditPage', 'upload' => 'ApiUpload', + 'filerevert' => 'ApiFileRevert', 'emailuser' => 'ApiEmailUser', 'watch' => 'ApiWatch', 'patrol' => 'ApiPatrol', @@ -123,7 +126,12 @@ class ApiMain extends ApiBase { ) ); - private $mPrinter, $mModules, $mModuleNames, $mFormats, $mFormatNames; + /** + * @var ApiFormatBase + */ + private $mPrinter; + + private $mModules, $mModuleNames, $mFormats, $mFormatNames; private $mResult, $mAction, $mShowVersions, $mEnableWrite, $mRequest; private $mInternalMode, $mSquidMaxage, $mModule; @@ -175,6 +183,7 @@ class ApiMain extends ApiBase { /** * Return true if the API was started by other PHP code using FauxRequest + * @return bool */ public function isInternalMode() { return $this->mInternalMode; @@ -199,6 +208,8 @@ class ApiMain extends ApiBase { /** * Get the API module object. Only works after executeAction() + * + * @return ApiBase */ public function getModule() { return $this->mModule; @@ -215,6 +226,8 @@ class ApiMain extends ApiBase { /** * Set how long the response should be cached. + * + * @param $maxage */ public function setCacheMaxAge( $maxage ) { $this->setCacheControl( array( @@ -268,7 +281,7 @@ class ApiMain extends ApiBase { } /** - * @deprecated Private caching is now the default, so there is usually no + * @deprecated since 1.17 Private caching is now the default, so there is usually no * need to call this function. If there is a need, you can use * $this->setCacheMode('private') */ @@ -283,6 +296,8 @@ class ApiMain extends ApiBase { * * Cache control values set here will only be used if the cache mode is not * private, see setCacheMode(). + * + * @param $directives array */ public function setCacheControl( $directives ) { $this->mCacheControl = $directives + $this->mCacheControl; @@ -296,7 +311,7 @@ class ApiMain extends ApiBase { * given URL must either always or never call this function; if it sometimes does and * sometimes doesn't, stuff will break. * - * @deprecated Use setCacheMode( 'anon-public-user-private' ) + * @deprecated since 1.17 Use setCacheMode( 'anon-public-user-private' ) */ public function setVaryCookie() { $this->setCacheMode( 'anon-public-user-private' ); @@ -304,6 +319,10 @@ class ApiMain extends ApiBase { /** * Create an instance of an output formatter by its name + * + * @param $format string + * + * @return ApiFormatBase */ public function createPrinterByName( $format ) { if ( !isset( $this->mFormats[$format] ) ) { @@ -343,22 +362,21 @@ class ApiMain extends ApiBase { wfDebugLog( 'exception', $e->getLogMessage() ); } - // // Handle any kind of exception by outputing properly formatted error message. // If this fails, an unhandled exception should be thrown so that global error // handler will process and log it. - // $errCode = $this->substituteResultWithError( $e ); // Error results should not be cached $this->setCacheMode( 'private' ); + $response = $this->getRequest()->response(); $headerStr = 'MediaWiki-API-Error: ' . $errCode; if ( $e->getCode() === 0 ) { - header( $headerStr ); + $response->header( $headerStr ); } else { - header( $headerStr, true, $e->getCode() ); + $response->header( $headerStr, true, $e->getCode() ); } // Reset and print just the error message @@ -381,29 +399,44 @@ class ApiMain extends ApiBase { } protected function sendCacheHeaders() { + global $wgUseXVO, $wgOut, $wgVaryOnXFP; + $response = $this->getRequest()->response(); + if ( $this->mCacheMode == 'private' ) { - header( 'Cache-Control: private' ); + $response->header( 'Cache-Control: private' ); return; } if ( $this->mCacheMode == 'anon-public-user-private' ) { - global $wgUseXVO, $wgOut; - header( 'Vary: Accept-Encoding, Cookie' ); + $xfp = $wgVaryOnXFP ? ', X-Forwarded-Proto' : ''; + $response->header( 'Vary: Accept-Encoding, Cookie' . $xfp ); if ( $wgUseXVO ) { - header( $wgOut->getXVO() ); + if ( $wgVaryOnXFP ) { + $wgOut->addVaryHeader( 'X-Forwarded-Proto' ); + } + $response->header( $wgOut->getXVO() ); if ( $wgOut->haveCacheVaryCookies() ) { // Logged in, mark this request private - header( 'Cache-Control: private' ); + $response->header( 'Cache-Control: private' ); return; } // Logged out, send normal public headers below } elseif ( session_id() != '' ) { // Logged in or otherwise has session (e.g. anonymous users who have edited) // Mark request private - header( 'Cache-Control: private' ); + $response->header( 'Cache-Control: private' ); return; } // else no XVO and anonymous, send public headers below } + + // Send public headers + if ( $wgVaryOnXFP ) { + $response->header( 'Vary: Accept-Encoding, X-Forwarded-Proto' ); + if ( $wgUseXVO ) { + // Bleeeeegh. Our header setting system sucks + $response->header( 'X-Vary-Options: Accept-Encoding;list-contains=gzip, X-Forwarded-Proto' ); + } + } // If nobody called setCacheMaxAge(), use the (s)maxage parameters if ( !isset( $this->mCacheControl['s-maxage'] ) ) { @@ -417,7 +450,7 @@ class ApiMain extends ApiBase { // Public cache not requested // Sending a Vary header in this case is harmless, and protects us // against conditional calls of setCacheMaxAge(). - header( 'Cache-Control: private' ); + $response->header( 'Cache-Control: private' ); return; } @@ -426,7 +459,7 @@ class ApiMain extends ApiBase { // Send an Expires header $maxAge = min( $this->mCacheControl['s-maxage'], $this->mCacheControl['max-age'] ); $expiryUnixTime = ( $maxAge == 0 ? 1 : time() + $maxAge ); - header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); + $response->header( 'Expires: ' . wfTimestamp( TS_RFC2822, $expiryUnixTime ) ); // Construct the Cache-Control header $ccHeader = ''; @@ -443,15 +476,17 @@ class ApiMain extends ApiBase { } } - header( "Cache-Control: $ccHeader" ); + $response->header( "Cache-Control: $ccHeader" ); } /** * Replace the result data with the information about an exception. * Returns the error code * @param $e Exception + * @return string */ protected function substituteResultWithError( $e ) { + $result = $this->getResult(); // Printer may not be initialized if the extractRequestParams() fails for the main module if ( !isset ( $this->mPrinter ) ) { // The printer has not been created yet. Try to manually get formatter value. @@ -462,14 +497,12 @@ class ApiMain extends ApiBase { $this->mPrinter = $this->createPrinterByName( $value ); if ( $this->mPrinter->getNeedsRawData() ) { - $this->getResult()->setRawMode(); + $result->setRawMode(); } } if ( $e instanceof UsageException ) { - // // User entered incorrect parameters - print usage screen - // $errMessage = $e->getMessageArray(); // Only print the help message when this is for the developer, not runtime @@ -479,9 +512,7 @@ class ApiMain extends ApiBase { } else { global $wgShowSQLErrors, $wgShowExceptionDetails; - // // Something is seriously wrong - // if ( ( $e instanceof DBQueryError ) && !$wgShowSQLErrors ) { $info = 'Database query error'; } else { @@ -495,32 +526,34 @@ class ApiMain extends ApiBase { ApiResult::setContent( $errMessage, $wgShowExceptionDetails ? "\n\n{$e->getTraceAsString()}\n\n" : '' ); } - $this->getResult()->reset(); - $this->getResult()->disableSizeCheck(); + $result->reset(); + $result->disableSizeCheck(); // Re-add the id $requestid = $this->getParameter( 'requestid' ); if ( !is_null( $requestid ) ) { - $this->getResult()->addValue( null, 'requestid', $requestid ); + $result->addValue( null, 'requestid', $requestid ); } // servedby is especially useful when debugging errors - $this->getResult()->addValue( null, 'servedby', wfHostName() ); - $this->getResult()->addValue( null, 'error', $errMessage ); + $result->addValue( null, 'servedby', wfHostName() ); + $result->addValue( null, 'error', $errMessage ); return $errMessage['code']; } /** * Set up for the execution. + * @return array */ protected function setupExecuteAction() { // First add the id to the top element + $result = $this->getResult(); $requestid = $this->getParameter( 'requestid' ); if ( !is_null( $requestid ) ) { - $this->getResult()->addValue( null, 'requestid', $requestid ); + $result->addValue( null, 'requestid', $requestid ); } $servedby = $this->getParameter( 'servedby' ); if ( $servedby ) { - $this->getResult()->addValue( null, 'servedby', wfHostName() ); + $result->addValue( null, 'servedby', wfHostName() ); } $params = $this->extractRequestParams(); @@ -553,8 +586,8 @@ class ApiMain extends ApiBase { $this->dieUsageMsg( array( 'missingparam', 'token' ) ); } else { global $wgUser; - if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt ) ) { - $this->dieUsageMsg( array( 'sessionfailure' ) ); + if ( !$wgUser->matchEditToken( $moduleParams['token'], $salt, $this->getRequest() ) ) { + $this->dieUsageMsg( 'sessionfailure' ); } } } @@ -574,8 +607,11 @@ class ApiMain extends ApiBase { $maxLag = $params['maxlag']; list( $host, $lag ) = wfGetLB()->getMaxLag(); if ( $lag > $maxLag ) { - header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); - header( 'X-Database-Lag: ' . intval( $lag ) ); + $response = $this->getRequest()->response(); + + $response->header( 'Retry-After: ' . max( intval( $maxLag ), 5 ) ); + $response->header( 'X-Database-Lag: ' . intval( $lag ) ); + if ( $wgShowHostnames ) { $this->dieUsage( "Waiting for $host: $lag seconds lagged", 'maxlag' ); } else { @@ -587,7 +623,6 @@ class ApiMain extends ApiBase { return true; } - /** * Check for sufficient permissions to execute * @param $module ApiBase An Api module @@ -597,14 +632,14 @@ class ApiMain extends ApiBase { if ( $module->isReadMode() && !in_array( 'read', User::getGroupPermissions( array( '*' ) ), true ) && !$wgUser->isAllowed( 'read' ) ) { - $this->dieUsageMsg( array( 'readrequired' ) ); + $this->dieUsageMsg( 'readrequired' ); } if ( $module->isWriteMode() ) { if ( !$this->mEnableWrite ) { - $this->dieUsageMsg( array( 'writedisabled' ) ); + $this->dieUsageMsg( 'writedisabled' ); } if ( !$wgUser->isAllowed( 'writeapi' ) ) { - $this->dieUsageMsg( array( 'writerequired' ) ); + $this->dieUsageMsg( 'writerequired' ); } if ( wfReadOnly() ) { $this->dieReadOnly(); @@ -666,6 +701,8 @@ class ApiMain extends ApiBase { /** * Print results using the current printer + * + * @param $isError bool */ protected function printResult( $isError ) { $this->getResult()->cleanUpUTF8(); @@ -687,12 +724,17 @@ class ApiMain extends ApiBase { $printer->profileOut(); } + /** + * @return bool + */ public function isReadMode() { return false; } /** * See ApiBase for description. + * + * @return array */ public function getAllowedParams() { return array( @@ -723,13 +765,22 @@ class ApiMain extends ApiBase { /** * See ApiBase for description. + * + * @return array */ public function getParamDescription() { return array( 'format' => 'The format of the output', 'action' => 'What action you would like to perform. See below for module help', 'version' => 'When showing help, include version for each module', - 'maxlag' => 'Maximum lag', + 'maxlag' => array( + 'Maximum lag can be used when MediaWiki is installed on a database replicated cluster.', + 'To save actions causing any more site replication lag, this parameter can make the client', + 'wait until the replication lag is less than the specified value.', + 'In case of a replag error, a HTTP 503 error is returned, with the message like', + '"Waiting for $host: $lag seconds lagged\n".', + 'See https://www.mediawiki.org/wiki/Manual:Maxlag_parameter for more information', + ), 'smaxage' => 'Set the s-maxage header to this many seconds. Errors are never cached', 'maxage' => 'Set the max-age header to this many seconds. Errors are never cached', 'requestid' => 'Request ID to distinguish requests. This will just be output back to you', @@ -739,28 +790,40 @@ class ApiMain extends ApiBase { /** * See ApiBase for description. + * + * @return array */ public function getDescription() { return array( '', '', - '******************************************************************************************', - '** **', - '** This is an auto-generated MediaWiki API documentation page **', - '** **', - '** Documentation and Examples: **', - '** http://www.mediawiki.org/wiki/API **', - '** **', - '******************************************************************************************', + '**********************************************************************************************************', + '** **', + '** This is an auto-generated MediaWiki API documentation page **', + '** **', + '** Documentation and Examples: **', + '** https://www.mediawiki.org/wiki/API **', + '** **', + '**********************************************************************************************************', '', 'Status: All features shown on this page should be working, but the API', - ' is still in active development, and may change at any time.', + ' is still in active development, and may change at any time.', ' Make sure to monitor our mailing list for any updates', '', - 'Documentation: http://www.mediawiki.org/wiki/API', - 'Mailing list: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api', - 'Api Announcements: http://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', - 'Bugs & Requests: http://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', + 'Erroneous requests: When erroneous requests are sent to the API, a HTTP header will be sent', + ' with the key "MediaWiki-API-Error" and then both the value of the', + ' header and the error code sent back will be set to the same value', + '', + ' In the case of an invalid action being passed, these will have a value', + ' of "unknown_action"', + '', + ' For more information see https://www.mediawiki.org/wiki/API:Errors_and_warnings', + '', + 'Documentation: https://www.mediawiki.org/wiki/API:Main_page', + 'FAQ https://www.mediawiki.org/wiki/API:FAQ', + 'Mailing list: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api', + 'Api Announcements: https://lists.wikimedia.org/mailman/listinfo/mediawiki-api-announce', + 'Bugs & Requests: https://bugzilla.wikimedia.org/buglist.cgi?component=API&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&order=bugs.delta_ts', '', '', '', @@ -769,6 +832,9 @@ class ApiMain extends ApiBase { ); } + /** + * @return array + */ public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( array( 'readonlytext' ), @@ -781,22 +847,26 @@ class ApiMain extends ApiBase { /** * Returns an array of strings with credits for the API + * @return array */ protected function getCredits() { return array( 'API developers:', - ' Roan Kattouw .@home.nl (lead developer Sep 2007-present)', + ' Roan Kattouw .@gmail.com (lead developer Sep 2007-present)', ' Victor Vasiliev - vasilvv at gee mail dot com', ' Bryan Tong Minh - bryan . tongminh @ gmail . com', ' Sam Reed - sam @ reedyboy . net', ' Yuri Astrakhan @gmail.com (creator, lead developer Sep 2006-Sep 2007)', '', 'Please send your comments, suggestions and questions to mediawiki-api@lists.wikimedia.org', - 'or file a bug report at http://bugzilla.wikimedia.org/' + 'or file a bug report at https://bugzilla.wikimedia.org/' ); } + /** * Sets whether the pretty-printer should format *bold* and $italics$ + * + * @param $help bool */ public function setHelp( $help = true ) { $this->mPrinter->setHelp( $help ); @@ -804,34 +874,39 @@ class ApiMain extends ApiBase { /** * Override the parent to generate help messages for all available modules. + * + * @return string */ public function makeHelpMsg() { - global $wgMemc, $wgAPICacheHelp, $wgAPICacheHelpTimeout; + global $wgMemc, $wgAPICacheHelpTimeout; $this->setHelp(); // Get help text from cache if present $key = wfMemcKey( 'apihelp', $this->getModuleName(), SpecialVersion::getVersion( 'nodb' ) . - $this->getMain()->getShowVersions() ); - if ( $wgAPICacheHelp ) { + $this->getShowVersions() ); + if ( $wgAPICacheHelpTimeout > 0 ) { $cached = $wgMemc->get( $key ); if ( $cached ) { return $cached; } } $retval = $this->reallyMakeHelpMsg(); - if ( $wgAPICacheHelp ) { + if ( $wgAPICacheHelpTimeout > 0 ) { $wgMemc->set( $key, $retval, $wgAPICacheHelpTimeout ); } return $retval; } + /** + * @return mixed|string + */ public function reallyMakeHelpMsg() { $this->setHelp(); // Use parent to make default message for the main module $msg = parent::makeHelpMsg(); - $astriks = str_repeat( '*** ', 10 ); + $astriks = str_repeat( '*** ', 14 ); $msg .= "\n\n$astriks Modules $astriks\n\n"; foreach ( array_keys( $this->mModules ) as $moduleName ) { $module = new $this->mModules[$moduleName] ( $this, $moduleName ); @@ -867,6 +942,11 @@ class ApiMain extends ApiBase { return $msg; } + /** + * @param $module ApiBase + * @param $paramName String What type of request is this? e.g. action, query, list, prop, meta, format + * @return string + */ public static function makeHelpMsgHeader( $module, $paramName ) { $modulePrefix = $module->getModulePrefix(); if ( strval( $modulePrefix ) !== '' ) { @@ -876,36 +956,8 @@ class ApiMain extends ApiBase { return "* $paramName={$module->getModuleName()} $modulePrefix*"; } - private $mIsBot = null; - private $mIsSysop = null; private $mCanApiHighLimits = null; - /** - * Returns true if the currently logged in user is a bot, false otherwise - * OBSOLETE, use canApiHighLimits() instead - */ - public function isBot() { - if ( !isset( $this->mIsBot ) ) { - global $wgUser; - $this->mIsBot = $wgUser->isAllowed( 'bot' ); - } - return $this->mIsBot; - } - - /** - * Similar to isBot(), this method returns true if the logged in user is - * a sysop, and false if not. - * OBSOLETE, use canApiHighLimits() instead - */ - public function isSysop() { - if ( !isset( $this->mIsSysop ) ) { - global $wgUser; - $this->mIsSysop = in_array( 'sysop', $wgUser->getGroups() ); - } - - return $this->mIsSysop; - } - /** * Check whether the current user is allowed to use high limits * @return bool @@ -930,11 +982,13 @@ class ApiMain extends ApiBase { /** * Returns the version information of this file, plus it includes * the versions for all files that are not callable proper API modules + * + * @return array */ public function getVersion() { - $vers = array (); - $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n http://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; - $vers[] = __CLASS__ . ': $Id: ApiMain.php 76196 2010-11-06 16:11:19Z reedy $'; + $vers = array(); + $vers[] = 'MediaWiki: ' . SpecialVersion::getVersion() . "\n https://svn.wikimedia.org/viewvc/mediawiki/trunk/phase3/"; + $vers[] = __CLASS__ . ': $Id: ApiMain.php 104449 2011-11-28 15:52:04Z reedy $'; $vers[] = ApiBase::getBaseVersion(); $vers[] = ApiFormatBase::getBaseVersion(); $vers[] = ApiQueryBase::getBaseVersion(); @@ -957,8 +1011,8 @@ class ApiMain extends ApiBase { * Add or overwrite an output format for this ApiMain. Intended for use by extending * classes who wish to add to or modify current formatters. * - * @param $fmtName The identifier for this format. - * @param $fmtClass The class implementing this format. + * @param $fmtName string The identifier for this format. + * @param $fmtClass ApiFormatBase The class implementing this format. */ protected function addFormat( $fmtName, $fmtClass ) { $this->mFormats[$fmtName] = $fmtClass; @@ -966,10 +1020,21 @@ class ApiMain extends ApiBase { /** * Get the array mapping module names to class names + * @return array */ function getModules() { return $this->mModules; } + + /** + * Returns the list of supported formats in form ( 'format' => 'ClassName' ) + * + * @since 1.18 + * @return array + */ + public function getFormats() { + return $this->mFormats; + } } /** @@ -989,10 +1054,16 @@ class UsageException extends Exception { $this->mExtraData = $extradata; } + /** + * @return string + */ public function getCodeString() { return $this->mCodestr; } + /** + * @return array + */ public function getMessageArray() { $result = array( 'code' => $this->mCodestr, @@ -1004,6 +1075,9 @@ class UsageException extends Exception { return $result; } + /** + * @return string + */ public function __toString() { return "{$this->getCodeString()}: {$this->getMessage()}"; } diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index a93188bf..f15a086c 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -61,7 +61,7 @@ class ApiMove extends ApiBase { } if ( !$fromTitle->exists() ) { - $this->dieUsageMsg( array( 'notanarticle' ) ); + $this->dieUsageMsg( 'notanarticle' ); } $fromTalk = $fromTitle->getTalkPage(); @@ -76,9 +76,9 @@ class ApiMove extends ApiBase { && wfFindFile( $toTitle ) ) { if ( !$params['ignorewarnings'] && $wgUser->isAllowed( 'reupload-shared' ) ) { - $this->dieUsageMsg( array( 'sharedfile-exists' ) ); + $this->dieUsageMsg( 'sharedfile-exists' ); } elseif ( !$wgUser->isAllowed( 'reupload-shared' ) ) { - $this->dieUsageMsg( array( 'cantoverwrite-sharedfile' ) ); + $this->dieUsageMsg( 'cantoverwrite-sharedfile' ); } } @@ -107,15 +107,18 @@ class ApiMove extends ApiBase { } } + $result = $this->getResult(); + // Move subpages if ( $params['movesubpages'] ) { $r['subpages'] = $this->moveSubpages( $fromTitle, $toTitle, $params['reason'], $params['noredirect'] ); - $this->getResult()->setIndexedTagName( $r['subpages'], 'subpage' ); + $result->setIndexedTagName( $r['subpages'], 'subpage' ); + if ( $params['movetalk'] ) { $r['subpages-talk'] = $this->moveSubpages( $fromTalk, $toTalk, $params['reason'], $params['noredirect'] ); - $this->getResult()->setIndexedTagName( $r['subpages-talk'], 'subpage' ); + $result->setIndexedTagName( $r['subpages-talk'], 'subpage' ); } } @@ -132,9 +135,16 @@ class ApiMove extends ApiBase { $this->setWatch( $watch, $fromTitle, 'watchmoves' ); $this->setWatch( $watch, $toTitle, 'watchmoves' ); - $this->getResult()->addValue( null, $this->getModuleName(), $r ); + $result->addValue( null, $this->getModuleName(), $r ); } + /** + * @param Title $fromTitle + * @param Title $toTitle + * @param $reason + * @param $noredirect + * @return array + */ public function moveSubpages( $fromTitle, $toTitle, $reason, $noredirect ) { $retval = array(); $success = $fromTitle->moveSubpages( $toTitle, true, $reason, !$noredirect ); @@ -224,13 +234,16 @@ class ApiMove extends ApiBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'invalidtitle', 'from' ), - array( 'nosuchpageid', 'fromid' ), - array( 'notanarticle' ), - array( 'invalidtitle', 'to' ), - array( 'sharedfile-exists' ), - ) ); + return array_merge( parent::getPossibleErrors(), + $this->getRequireOnlyOneParameterErrorMessages( array( 'from', 'fromid' ) ), + array( + array( 'invalidtitle', 'from' ), + array( 'nosuchpageid', 'fromid' ), + array( 'notanarticle' ), + array( 'invalidtitle', 'to' ), + array( 'sharedfile-exists' ), + ) + ); } public function needsToken() { @@ -247,7 +260,11 @@ class ApiMove extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Move'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiMove.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiMove.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index 885766d2..084c9860 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -1,6 +1,6 @@ 0 ) { @@ -116,7 +116,7 @@ class ApiOpenSearch extends ApiBase { } public function getDescription() { - return 'This module implements OpenSearch protocol'; + return 'Searches the wiki using the OpenSearch protocol'; } protected function getExamples() { @@ -125,7 +125,11 @@ class ApiOpenSearch extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Opensearch'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiOpenSearch.php 79720 2011-01-06 14:48:34Z catrope $'; + return __CLASS__ . ': $Id: ApiOpenSearch.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 1cb12c07..cf10a9ac 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -1,6 +1,6 @@ prefixed_title (string) + * @return array prefixed_title (string) => Title object */ public function getRedirectTitles() { return $this->mRedirectTitles; @@ -351,7 +352,7 @@ class ApiPageSet extends ApiQueryBase { */ public function populateFromQueryResult( $db, $queryResult ) { $this->profileIn(); - $this->initFromQueryResult( $db, $queryResult ); + $this->initFromQueryResult( $queryResult ); $this->profileOut(); } @@ -430,7 +431,7 @@ class ApiPageSet extends ApiQueryBase { $this->profileDBOut(); // Hack: get the ns:titles stored in array(ns => array(titles)) format - $this->initFromQueryResult( $db, $res, $linkBatch->data, true ); // process Titles + $this->initFromQueryResult( $res, $linkBatch->data, true ); // process Titles // Resolve any found redirects $this->resolvePendingRedirects(); @@ -446,19 +447,25 @@ class ApiPageSet extends ApiQueryBase { } $pageids = array_map( 'intval', $pageids ); // paranoia - $set = array( - 'page_id' => $pageids - ); - $db = $this->getDB(); + $remaining = array_flip( $pageids ); - // Get pageIDs data from the `page` table - $this->profileDBIn(); - $res = $db->select( 'page', $this->getPageTableFields(), $set, - __METHOD__ ); - $this->profileDBOut(); + $pageids = self::getPositiveIntegers( $pageids ); - $remaining = array_flip( $pageids ); - $this->initFromQueryResult( $db, $res, $remaining, false ); // process PageIDs + $res = null; + if ( count( $pageids ) ) { + $set = array( + 'page_id' => $pageids + ); + $db = $this->getDB(); + + // Get pageIDs data from the `page` table + $this->profileDBIn(); + $res = $db->select( 'page', $this->getPageTableFields(), $set, + __METHOD__ ); + $this->profileDBOut(); + } + + $this->initFromQueryResult( $res, $remaining, false ); // process PageIDs // Resolve any found redirects $this->resolvePendingRedirects(); @@ -467,7 +474,6 @@ class ApiPageSet extends ApiQueryBase { /** * Iterate through the result of the query on 'page' table, * and for each row create and store title object and save any extra fields requested. - * @param $db Database * @param $res ResultWrapper DB Query result * @param $remaining array of either pageID or ns/title elements (optional). * If given, any missing items will go to $mMissingPageIDs and $mMissingTitles @@ -475,25 +481,27 @@ class ApiPageSet extends ApiQueryBase { * If true, treat $remaining as an array of [ns][title] * If false, treat it as an array of [pageIDs] */ - private function initFromQueryResult( $db, $res, &$remaining = null, $processTitles = null ) { + private function initFromQueryResult( $res, &$remaining = null, $processTitles = null ) { if ( !is_null( $remaining ) && is_null( $processTitles ) ) { ApiBase::dieDebug( __METHOD__, 'Missing $processTitles parameter when $remaining is provided' ); } - foreach ( $res as $row ) { - $pageId = intval( $row->page_id ); + if ( $res ) { + foreach ( $res as $row ) { + $pageId = intval( $row->page_id ); - // Remove found page from the list of remaining items - if ( isset( $remaining ) ) { - if ( $processTitles ) { - unset( $remaining[$row->page_namespace][$row->page_title] ); - } else { - unset( $remaining[$pageId] ); + // Remove found page from the list of remaining items + if ( isset( $remaining ) ) { + if ( $processTitles ) { + unset( $remaining[$row->page_namespace][$row->page_title] ); + } else { + unset( $remaining[$pageId] ); + } } - } - // Store any extra fields requested by modules - $this->processDbRow( $row ); + // Store any extra fields requested by modules + $this->processDbRow( $row ); + } } if ( isset( $remaining ) ) { @@ -535,21 +543,25 @@ class ApiPageSet extends ApiQueryBase { $pageids = array(); $remaining = array_flip( $revids ); - $tables = array( 'revision', 'page' ); - $fields = array( 'rev_id', 'rev_page' ); - $where = array( 'rev_id' => $revids, 'rev_page = page_id' ); - - // Get pageIDs data from the `page` table - $this->profileDBIn(); - $res = $db->select( $tables, $fields, $where, __METHOD__ ); - foreach ( $res as $row ) { - $revid = intval( $row->rev_id ); - $pageid = intval( $row->rev_page ); - $this->mGoodRevIDs[$revid] = $pageid; - $pageids[$pageid] = ''; - unset( $remaining[$revid] ); + $revids = self::getPositiveIntegers( $revids ); + + if ( count( $revids ) ) { + $tables = array( 'revision', 'page' ); + $fields = array( 'rev_id', 'rev_page' ); + $where = array( 'rev_id' => $revids, 'rev_page = page_id' ); + + // Get pageIDs data from the `page` table + $this->profileDBIn(); + $res = $db->select( $tables, $fields, $where, __METHOD__ ); + foreach ( $res as $row ) { + $revid = intval( $row->rev_id ); + $pageid = intval( $row->rev_page ); + $this->mGoodRevIDs[$revid] = $pageid; + $pageids[$pageid] = ''; + unset( $remaining[$revid] ); + } + $this->profileDBOut(); } - $this->profileDBOut(); $this->mMissingRevIDs = array_keys( $remaining ); @@ -589,7 +601,7 @@ class ApiPageSet extends ApiQueryBase { $this->profileDBOut(); // Hack: get the ns:titles stored in array(ns => array(titles)) format - $this->initFromQueryResult( $db, $res, $linkBatch->data, true ); + $this->initFromQueryResult( $res, $linkBatch->data, true ); } } } @@ -611,16 +623,17 @@ class ApiPageSet extends ApiQueryBase { array( 'rd_from', 'rd_namespace', + 'rd_fragment', + 'rd_interwiki', 'rd_title' ), array( 'rd_from' => array_keys( $this->mPendingRedirectIDs ) ), __METHOD__ ); $this->profileDBOut(); - foreach ( $res as $row ) { $rdfrom = intval( $row->rd_from ); $from = $this->mPendingRedirectIDs[$rdfrom]->getPrefixedText(); - $to = Title::makeTitle( $row->rd_namespace, $row->rd_title )->getPrefixedText(); + $to = Title::makeTitle( $row->rd_namespace, $row->rd_title, $row->rd_fragment, $row->rd_interwiki ); unset( $this->mPendingRedirectIDs[$rdfrom] ); if ( !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) { $lb->add( $row->rd_namespace, $row->rd_title ); @@ -639,7 +652,7 @@ class ApiPageSet extends ApiQueryBase { continue; } $lb->addObj( $rt ); - $this->mRedirectTitles[$title->getPrefixedText()] = $rt->getPrefixedText(); + $this->mRedirectTitles[$title->getPrefixedText()] = $rt; unset( $this->mPendingRedirectIDs[$id] ); } } @@ -685,7 +698,6 @@ class ApiPageSet extends ApiQueryBase { $titleWasConverted = $unconvertedTitle !== $titleObj->getPrefixedText(); } - if ( $titleObj->getNamespace() < 0 ) { // Handle Special and Media pages $titleObj = $titleObj->fixSpecialName(); @@ -712,6 +724,25 @@ class ApiPageSet extends ApiQueryBase { return $linkBatch; } + /** + * Returns the input array of integers with all values < 0 removed + * + * @param $array array + * @return array + */ + private static function getPositiveIntegers( $array ) { + // bug 25734 API: possible issue with revids validation + // It seems with a load of revision rows, MySQL gets upset + // Remove any < 0 integers, as they can't be valid + foreach( $array as $i => $int ) { + if ( $int < 0 ) { + unset( $array[$i] ); + } + } + + return $array; + } + protected function getAllowedParams() { return array( 'titles' => array( @@ -744,6 +775,6 @@ class ApiPageSet extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiPageSet.php 76196 2010-11-06 16:11:19Z reedy $'; + return __CLASS__ . ': $Id: ApiPageSet.php 89574 2011-06-06 15:58:55Z reedy $'; } } diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index a2c0bd11..5670b041 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -70,6 +70,7 @@ class ApiParamInfo extends ApiBase { $obj = new $qmodArr[$qm]( $this, $qm ); $a = $this->getClassInfo( $obj ); $a['name'] = $qm; + $a['querytype'] = $queryObj->getModuleType( $qm ); $r['querymodules'][] = $a; } $result->setIndexedTagName( $r['querymodules'], 'module' ); @@ -84,11 +85,16 @@ class ApiParamInfo extends ApiBase { $result->addValue( null, $this->getModuleName(), $r ); } + /** + * @param $obj ApiBase + * @return ApiResult + */ function getClassInfo( $obj ) { $result = $this->getResult(); $retval['classname'] = get_class( $obj ); $retval['description'] = implode( "\n", (array)$obj->getDescription() ); - $retval['examples'] = implode( "\n", (array)$obj->getExamples() ); + $examples = (array)$obj->getExamples(); + $retval['examples'] = implode( "\n", $examples ); $retval['version'] = implode( "\n", (array)$obj->getVersion() ); $retval['prefix'] = $obj->getModulePrefix(); @@ -110,6 +116,18 @@ class ApiParamInfo extends ApiBase { return $retval; } + $retval['helpurls'] = (array)$obj->getHelpUrls(); + if ( isset( $retval['helpurls'][0] ) && $retval['helpurls'][0] === false ) { + $retval['helpurls'] = array(); + } + $result->setIndexedTagName( $retval['helpurls'], 'helpurl' ); + + $retval['allexamples'] = $examples; + if ( isset( $retval['allexamples'][0] ) && $retval['allexamples'][0] === false ) { + $retval['allexamples'] = array(); + } + $result->setIndexedTagName( $retval['allexamples'], 'example' ); + $retval['parameters'] = array(); $paramDesc = $obj->getFinalParamDescription(); foreach ( $allowedParams as $n => $p ) { @@ -117,6 +135,26 @@ class ApiParamInfo extends ApiBase { if ( isset( $paramDesc[$n] ) ) { $a['description'] = implode( "\n", (array)$paramDesc[$n] ); } + + //handle shorthand + if( !is_array( $p ) ) { + $p = array( + ApiBase::PARAM_DFLT => $p, + ); + } + + //handle missing type + if ( !isset( $p[ApiBase::PARAM_TYPE] ) ) { + $dflt = isset( $p[ApiBase::PARAM_DFLT] ) ? $p[ApiBase::PARAM_DFLT] : null; + if ( is_bool( $dflt ) ) { + $p[ApiBase::PARAM_TYPE] = 'boolean'; + } elseif ( is_string( $dflt ) || is_null( $dflt ) ) { + $p[ApiBase::PARAM_TYPE] = 'string'; + } elseif ( is_int( $dflt ) ) { + $p[ApiBase::PARAM_TYPE] = 'integer'; + } + } + if ( isset( $p[ApiBase::PARAM_DEPRECATED] ) && $p[ApiBase::PARAM_DEPRECATED] ) { $a['deprecated'] = ''; } @@ -124,29 +162,25 @@ class ApiParamInfo extends ApiBase { $a['required'] = ''; } - if ( !is_array( $p ) ) { - if ( is_bool( $p ) ) { - $a['type'] = 'bool'; - $a['default'] = ( $p ? 'true' : 'false' ); - } elseif ( is_string( $p ) || is_null( $p ) ) { - $a['type'] = 'string'; - $a['default'] = strval( $p ); - } elseif ( is_int( $p ) ) { - $a['type'] = 'integer'; - $a['default'] = intval( $p ); - } - $retval['parameters'][] = $a; - continue; - } - if ( isset( $p[ApiBase::PARAM_DFLT] ) ) { - $a['default'] = $p[ApiBase::PARAM_DFLT]; + $type = $p[ApiBase::PARAM_TYPE]; + if( $type === 'boolean' ) { + $a['default'] = ( $p[ApiBase::PARAM_DFLT] ? 'true' : 'false' ); + } elseif( $type === 'string' ) { + $a['default'] = strval( $p[ApiBase::PARAM_DFLT] ); + } elseif( $type === 'integer' ) { + $a['default'] = intval( $p[ApiBase::PARAM_DFLT] ); + } else { + $a['default'] = $p[ApiBase::PARAM_DFLT]; + } } if ( isset( $p[ApiBase::PARAM_ISMULTI] ) && $p[ApiBase::PARAM_ISMULTI] ) { $a['multi'] = ''; $a['limit'] = $this->getMain()->canApiHighLimits() ? - ApiBase::LIMIT_SML2 : - ApiBase::LIMIT_SML1; + ApiBase::LIMIT_SML2 : + ApiBase::LIMIT_SML1; + $a['lowlimit'] = ApiBase::LIMIT_SML1; + $a['highlimit'] = ApiBase::LIMIT_SML2; } if ( isset( $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) && $p[ApiBase::PARAM_ALLOW_DUPLICATES] ) { @@ -175,7 +209,6 @@ class ApiParamInfo extends ApiBase { // Errors $retval['errors'] = $this->parseErrors( $obj->getPossibleErrors() ); - $result->setIndexedTagName( $retval['errors'], 'error' ); return $retval; @@ -217,7 +250,11 @@ class ApiParamInfo extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Parameter_information'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiParamInfo.php 87170 2011-04-30 16:57:22Z catrope $'; + return __CLASS__ . ': $Id: ApiParamInfo.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 2d12c233..6212b4ad 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -1,7 +1,5 @@ @gmail.com @@ -33,7 +31,6 @@ if ( !defined( 'MEDIAWIKI' ) ) { * @ingroup API */ class ApiParse extends ApiBase { - private $section, $text, $pstText = null; public function __construct( $main, $action ) { @@ -80,8 +77,10 @@ class ApiParse extends ApiBase { $redirValues = null; - if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) { + // Return result + $result = $this->getResult(); + if ( !is_null( $oldid ) || !is_null( $pageid ) || !is_null( $page ) ) { if ( !is_null( $oldid ) ) { // Don't use the parser cache $rev = Revision::newFromID( $oldid ); @@ -96,13 +95,12 @@ class ApiParse extends ApiBase { $wgTitle = $titleObj; - //If for some reason the "oldid" is actually the current revision, it may be cached - if ( $titleObj->getLatestRevID() === $oldid ) { + // If for some reason the "oldid" is actually the current revision, it may be cached + if ( $titleObj->getLatestRevID() === intval( $oldid ) ) { $articleObj = new Article( $titleObj, 0 ); $p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid, isset( $prop['wikitext'] ) ) ; - } else { // This is an old revision, so get the text differently $this->text = $rev->getText( Revision::FOR_THIS_USER ); @@ -114,38 +112,45 @@ class ApiParse extends ApiBase { $p_result = $wgParser->parse( $this->text, $titleObj, $popts ); } - } else { // Not $oldid - - if ( !is_null ( $pageid ) ) { - $titleObj = Title::newFromID( $pageid ); - - if ( !$titleObj ) { - $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) ); + if ( $params['redirects'] ) { + $reqParams = array( + 'action' => 'query', + 'redirects' => '', + ); + if ( !is_null ( $pageid ) ) { + $reqParams['pageids'] = $pageid; + } else { // $page + $reqParams['titles'] = $page; } - } else { // $page - - if ( $params['redirects'] ) { - $req = new FauxRequest( array( - 'action' => 'query', - 'redirects' => '', - 'titles' => $page - ) ); - $main = new ApiMain( $req ); - $main->execute(); - $data = $main->getResultData(); - $redirValues = @$data['query']['redirects']; - $to = $page; - foreach ( (array)$redirValues as $r ) { - $to = $r['to']; - } - } else { - $to = $page; + $req = new FauxRequest( $reqParams ); + $main = new ApiMain( $req ); + $main->execute(); + $data = $main->getResultData(); + $redirValues = isset( $data['query']['redirects'] ) + ? $data['query']['redirects'] + : array(); + $to = $page; + foreach ( (array)$redirValues as $r ) { + $to = $r['to']; } $titleObj = Title::newFromText( $to ); - if ( !$titleObj || !$titleObj->exists() ) { - $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' ); + } else { + if ( !is_null ( $pageid ) ) { + $reqParams['pageids'] = $pageid; + $titleObj = Title::newFromID( $pageid ); + } else { // $page + $to = $page; + $titleObj = Title::newFromText( $to ); + } + } + if ( !is_null ( $pageid ) ) { + if ( !$titleObj ) { + // Still throw nosuchpageid error if pageid was provided + $this->dieUsageMsg( array( 'nosuchpageid', $pageid ) ); } + } elseif ( !$titleObj || !$titleObj->exists() ) { + $this->dieUsage( "The page you specified doesn't exist", 'missingtitle' ); } $wgTitle = $titleObj; @@ -157,13 +162,12 @@ class ApiParse extends ApiBase { $p_result = $this->getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageid, isset( $prop['wikitext'] ) ) ; } - } else { // Not $oldid, $pageid, $page. Hence based on $text $this->text = $text; $titleObj = Title::newFromText( $title ); if ( !$titleObj ) { - $titleObj = Title::newFromText( 'API' ); + $this->dieUsageMsg( array( 'invalidtitle', $title ) ); } $wgTitle = $titleObj; @@ -177,20 +181,25 @@ class ApiParse extends ApiBase { if ( $params['onlypst'] ) { // Build a result and bail out $result_array['text'] = array(); - $this->getResult()->setContent( $result_array['text'], $this->pstText ); + $result->setContent( $result_array['text'], $this->pstText ); if ( isset( $prop['wikitext'] ) ) { $result_array['wikitext'] = array(); - $this->getResult()->setContent( $result_array['wikitext'], $this->text ); + $result->setContent( $result_array['wikitext'], $this->text ); } - $this->getResult()->addValue( null, $this->getModuleName(), $result_array ); + $result->addValue( null, $this->getModuleName(), $result_array ); return; } $p_result = $wgParser->parse( $params['pst'] ? $this->pstText : $this->text, $titleObj, $popts ); } - // Return result - $result = $this->getResult(); $result_array = array(); + + $result_array['title'] = $titleObj->getPrefixedText(); + + if ( !is_null( $oldid ) ) { + $result_array['revid'] = intval( $oldid ); + } + if ( $params['redirects'] && !is_null( $redirValues ) ) { $result_array['redirects'] = $redirValues; } @@ -244,31 +253,30 @@ class ApiParse extends ApiBase { } if ( isset( $prop['headitems'] ) || isset( $prop['headhtml'] ) ) { - $out = new OutputPage; - $out->addParserOutputNoText( $p_result ); - $userSkin = $wgUser->getSkin(); - } + $context = new RequestContext; + $context->getOutput()->addParserOutputNoText( $p_result ); - if ( isset( $prop['headitems'] ) ) { - $headItems = $this->formatHeadItems( $p_result->getHeadItems() ); + if ( isset( $prop['headitems'] ) ) { + $headItems = $this->formatHeadItems( $p_result->getHeadItems() ); - $userSkin->setupUserCss( $out ); - $css = $this->formatCss( $out->buildCssLinksArray() ); + $context->getSkin()->setupUserCss( $context->getOutput() ); + $css = $this->formatCss( $context->getOutput()->buildCssLinksArray() ); - $scripts = array( $out->getHeadScripts( $userSkin ) ); + $scripts = array( $context->getOutput()->getHeadScripts( $context->getSkin() ) ); - $result_array['headitems'] = array_merge( $headItems, $css, $scripts ); - } + $result_array['headitems'] = array_merge( $headItems, $css, $scripts ); + } - if ( isset( $prop['headhtml'] ) ) { - $result_array['headhtml'] = array(); - $result->setContent( $result_array['headhtml'], $out->headElement( $userSkin ) ); + if ( isset( $prop['headhtml'] ) ) { + $result_array['headhtml'] = array(); + $result->setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) ); + } } if ( isset( $prop['iwlinks'] ) ) { $result_array['iwlinks'] = $this->formatIWLinks( $p_result->getInterwikiLinks() ); } - + if ( isset( $prop['wikitext'] ) ) { $result_array['wikitext'] = array(); $result->setContent( $result_array['wikitext'], $this->text ); @@ -278,10 +286,6 @@ class ApiParse extends ApiBase { } } - if ( !is_null( $oldid ) ) { - $result_array['revid'] = intval( $oldid ); - } - $result_mapping = array( 'redirects' => 'r', 'langlinks' => 'll', @@ -303,11 +307,11 @@ class ApiParse extends ApiBase { } /** - * @param $articleObj Article - * @param $titleObj Title - * @param $popts ParserOptions - * @param $pageId Int - * @param $getWikitext Bool + * @param $articleObj Article + * @param $titleObj Title + * @param $popts ParserOptions + * @param $pageId Int + * @param $getWikitext Bool * @return ParserOutput */ private function getParsedSectionOrText( $articleObj, $titleObj, $popts, $pageId = null, $getWikitext = false ) { @@ -346,10 +350,10 @@ class ApiParse extends ApiBase { $entry = array(); $bits = explode( ':', $link, 2 ); $title = Title::newFromText( $link ); - + $entry['lang'] = $bits[0]; if ( $title ) { - $entry['url'] = $title->getFullURL(); + $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } $this->getResult()->setContent( $entry, $bits[1] ); $result[] = $entry; @@ -369,17 +373,41 @@ class ApiParse extends ApiBase { } private function categoriesHtml( $categories ) { - global $wgOut, $wgUser; - $wgOut->addCategoryLinks( $categories ); - $sk = $wgUser->getSkin(); - return $sk->getCategories(); + $context = $this->createContext(); + $context->getOutput()->addCategoryLinks( $categories ); + return $context->getSkin()->getCategories(); } + /** + * @deprecated since 1.18 No modern skin generates language links this way, please use language links + * data to generate your own HTML. + */ private function languagesHtml( $languages ) { - global $wgOut, $wgUser; - $wgOut->setLanguageLinks( $languages ); - $sk = $wgUser->getSkin(); - return $sk->otherLanguages(); + global $wgContLang, $wgHideInterlanguageLinks; + + if ( $wgHideInterlanguageLinks || count( $languages ) == 0 ) { + return ''; + } + + $s = htmlspecialchars( wfMsg( 'otherlanguages' ) . wfMsg( 'colon-separator' ) ); + + $langs = array(); + foreach ( $languages as $l ) { + $nt = Title::newFromText( $l ); + $text = $wgContLang->getLanguageName( $nt->getInterwiki() ); + + $langs[] = Html::element( 'a', + array( 'href' => $nt->getFullURL(), 'title' => $nt->getText(), 'class' => "external" ), + $text == '' ? $l : $text ); + } + + $s .= implode( htmlspecialchars( wfMsgExt( 'pipe-separator', 'escapenoentities' ) ), $langs ); + + if ( $wgContLang->isRTL() ) { + $s = Html::rawElement( 'span', array( 'dir' => "LTR" ), $s ); + } + + return $s; } private function formatLinks( $links ) { @@ -407,7 +435,7 @@ class ApiParse extends ApiBase { $title = Title::newFromText( "{$prefix}:{$title}" ); if ( $title ) { - $entry['url'] = $title->getFullURL(); + $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } $this->getResult()->setContent( $entry, $title->getFullText() ); @@ -455,9 +483,13 @@ class ApiParse extends ApiBase { 'text' => null, 'summary' => null, 'page' => null, - 'pageid' => null, + 'pageid' => array( + ApiBase::PARAM_TYPE => 'integer', + ), 'redirects' => false, - 'oldid' => null, + 'oldid' => array( + ApiBase::PARAM_TYPE => 'integer', + ), 'prop' => array( ApiBase::PARAM_DFLT => 'text|langlinks|categories|links|templates|images|externallinks|sections|revid|displaytitle', ApiBase::PARAM_ISMULTI => true, @@ -493,7 +525,7 @@ class ApiParse extends ApiBase { return array( 'text' => 'Wikitext to parse', 'summary' => 'Summary to parse', - 'redirects' => "If the {$p}page parameter is set to a redirect, resolve it", + 'redirects' => "If the {$p}page or the {$p}pageid parameter is set to a redirect, resolve it", 'title' => 'Title of page the text belongs to', 'page' => "Parse the content of this page. Cannot be used together with {$p}text and {$p}title", 'pageid' => "Parse the content of this page. Overrides {$p}page", @@ -501,16 +533,16 @@ class ApiParse extends ApiBase { 'prop' => array( 'Which pieces of information to get', ' text - Gives the parsed text of the wikitext', - ' langlinks - Gives the langlinks the parsed wikitext', - ' categories - Gives the categories of the parsed wikitext', - ' categorieshtml - Gives the html version of the categories', - ' languageshtml - Gives the html version of the languagelinks', + ' langlinks - Gives the language links in the parsed wikitext', + ' categories - Gives the categories in the parsed wikitext', + ' categorieshtml - Gives the HTML version of the categories', + ' languageshtml - Gives the HTML version of the language links', ' links - Gives the internal links in the parsed wikitext', ' templates - Gives the templates in the parsed wikitext', ' images - Gives the images in the parsed wikitext', ' externallinks - Gives the external links in the parsed wikitext', ' sections - Gives the sections in the parsed wikitext', - ' revid - Adds the revision id of the parsed page', + ' revid - Adds the revision ID of the parsed page', ' displaytitle - Adds the title of the parsed wikitext', ' headitems - Gives items to put in the of the page', ' headhtml - Gives parsed of the page', @@ -532,7 +564,7 @@ class ApiParse extends ApiBase { } public function getDescription() { - return 'This module parses wikitext and returns parser output'; + return 'Parses wikitext and returns parser output'; } public function getPossibleErrors() { @@ -543,6 +575,7 @@ class ApiParse extends ApiBase { array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ), array( 'code' => 'nosuchsection', 'info' => 'There is no section sectionnumber in page' ), array( 'nosuchpageid' ), + array( 'invalidtitle', 'title' ), ) ); } @@ -552,7 +585,11 @@ class ApiParse extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Parsing_wikitext#parse'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiParse.php 89672 2011-06-07 18:45:20Z catrope $'; + return __CLASS__ . ': $Id: ApiParse.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php index 08835743..8066e655 100644 --- a/includes/api/ApiPatrol.php +++ b/includes/api/ApiPatrol.php @@ -42,13 +42,15 @@ class ApiPatrol extends ApiBase { * Patrols the article or provides the reason the patrol failed. */ public function execute() { + global $wgUser; + $params = $this->extractRequestParams(); $rc = RecentChange::newFromID( $params['rcid'] ); if ( !$rc instanceof RecentChange ) { $this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) ); } - $retval = RecentChange::markPatrolled( $params['rcid'] ); + $retval = $rc->doMarkPatrolled( $wgUser ); if ( $retval ) { $this->dieUsageMsg( reset( $retval ) ); @@ -108,7 +110,11 @@ class ApiPatrol extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Patrol'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiPatrol.php 78437 2010-12-15 14:14:16Z catrope $'; + return __CLASS__ . ': $Id: ApiPatrol.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 3a1d18e0..5556262e 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,6 +63,7 @@ class ApiProtect extends ApiBase { } $restrictionTypes = $titleObj->getRestrictionTypes(); + $dbr = wfGetDB( DB_SLAVE ); $protections = array(); $expiryarray = array(); @@ -72,10 +73,10 @@ class ApiProtect extends ApiBase { $protections[$p[0]] = ( $p[1] == 'all' ? '' : $p[1] ); if ( $titleObj->exists() && $p[0] == 'create' ) { - $this->dieUsageMsg( array( 'create-titleexists' ) ); + $this->dieUsageMsg( 'create-titleexists' ); } if ( !$titleObj->exists() && $p[0] != 'create' ) { - $this->dieUsageMsg( array( 'missingtitle-createonly' ) ); + $this->dieUsageMsg( 'missingtitle-createonly' ); } if ( !in_array( $p[0], $restrictionTypes ) && $p[0] != 'create' ) { @@ -86,7 +87,7 @@ class ApiProtect extends ApiBase { } if ( in_array( $expiry[$i], array( 'infinite', 'indefinite', 'never' ) ) ) { - $expiryarray[$p[0]] = Block::infinity(); + $expiryarray[$p[0]] = $dbr->getInfinity(); } else { $exp = strtotime( $expiry[$i] ); if ( $exp < 0 || !$exp ) { @@ -100,7 +101,7 @@ class ApiProtect extends ApiBase { $expiryarray[$p[0]] = $exp; } $resultProtections[] = array( $p[0] => $protections[$p[0]], - 'expiry' => ( $expiryarray[$p[0]] == Block::infinity() ? + 'expiry' => ( $expiryarray[$p[0]] == $dbr->getInfinity() ? 'infinite' : wfTimestamp( TS_ISO_8601, $expiryarray[$p[0]] ) ) ); } @@ -129,8 +130,9 @@ class ApiProtect extends ApiBase { $res['cascade'] = ''; } $res['protections'] = $resultProtections; - $this->getResult()->setIndexedTagName( $res['protections'], 'protection' ); - $this->getResult()->addValue( null, $this->getModuleName(), $res ); + $result = $this->getResult(); + $result->setIndexedTagName( $res['protections'], 'protection' ); + $result->addValue( null, $this->getModuleName(), $res ); } public function mustBePosted() { @@ -222,7 +224,11 @@ class ApiProtect extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Protect'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiProtect.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiProtect.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php index a17abf16..bdf911cb 100644 --- a/includes/api/ApiPurge.php +++ b/includes/api/ApiPurge.php @@ -49,6 +49,9 @@ class ApiPurge extends ApiBase { !$this->getMain()->getRequest()->wasPosted() ) { $this->dieUsageMsg( array( 'mustbeposted', $this->getModuleName() ) ); } + + $forceLinkUpdate = $params['forcelinkupdate']; + $result = array(); foreach ( $params['titles'] as $t ) { $r = array(); @@ -65,13 +68,39 @@ class ApiPurge extends ApiBase { $result[] = $r; continue; } - $article = MediaWiki::articleFromTitle( $title ); + $context = $this->createContext(); + $context->setTitle( $title ); + $article = Article::newFromTitle( $title, $context ); $article->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = ''; + + if( $forceLinkUpdate ) { + if ( !$wgUser->pingLimiter() ) { + global $wgParser, $wgEnableParserCache; + $popts = new ParserOptions(); + $p_result = $wgParser->parse( $article->getContent(), $title, $popts ); + + # Update the links tables + $u = new LinksUpdate( $title, $p_result ); + $u->doUpdate(); + + $r['linkupdate'] = ''; + + if ( $wgEnableParserCache ) { + $pcache = ParserCache::singleton(); + $pcache->save( $p_result, $article, $popts ); + } + } else { + $this->setWarning( $this->parseMsg( array( 'actionthrottledtext' ) ) ); + $forceLinkUpdate = false; + } + } + $result[] = $r; } - $this->getResult()->setIndexedTagName( $result, 'page' ); - $this->getResult()->addValue( null, $this->getModuleName(), $result ); + $apiResult = $this->getResult(); + $apiResult->setIndexedTagName( $result, 'page' ); + $apiResult->addValue( null, $this->getModuleName(), $result ); } public function isWriteMode() { @@ -83,19 +112,21 @@ class ApiPurge extends ApiBase { 'titles' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_REQUIRED => true - ) + ), + 'forcelinkupdate' => false, ); } public function getParamDescription() { return array( 'titles' => 'A list of titles', + 'forcelinkupdate' => 'Update the links tables', ); } public function getDescription() { return array( 'Purge the cache for the given titles.', - 'This module requires a POST request if the user is not logged in.' + 'Requires a POST request if the user is not logged in.' ); } @@ -111,7 +142,11 @@ class ApiPurge extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Purge'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiPurge.php 74944 2010-10-18 09:19:20Z catrope $'; + return __CLASS__ . ': $Id: ApiPurge.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index f88aa850..49fd8569 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -1,6 +1,6 @@ 'ApiQueryInfo', @@ -77,6 +82,7 @@ class ApiQuery extends ApiBase { 'filearchive' => 'ApiQueryFilearchive', 'imageusage' => 'ApiQueryBacklinks', 'iwbacklinks' => 'ApiQueryIWBacklinks', + 'langbacklinks' => 'ApiQueryLangBacklinks', 'logevents' => 'ApiQueryLogEvents', 'recentchanges' => 'ApiQueryRecentChanges', 'search' => 'ApiQuerySearch', @@ -88,6 +94,7 @@ class ApiQuery extends ApiBase { 'users' => 'ApiQueryUsers', 'random' => 'ApiQueryRandom', 'protectedtitles' => 'ApiQueryProtectedTitles', + 'querypage' => 'ApiQueryQueryPage', ); private $mQueryMetaModules = array( @@ -184,15 +191,15 @@ class ApiQuery extends ApiBase { * @return mixed string or null */ function getModuleType( $moduleName ) { - if ( array_key_exists ( $moduleName, $this->mQueryPropModules ) ) { + if ( isset( $this->mQueryPropModules[$moduleName] ) ) { return 'prop'; } - if ( array_key_exists ( $moduleName, $this->mQueryListModules ) ) { + if ( isset( $this->mQueryListModules[$moduleName] ) ) { return 'list'; } - if ( array_key_exists ( $moduleName, $this->mQueryMetaModules ) ) { + if ( isset( $this->mQueryMetaModules[$moduleName] ) ) { return 'meta'; } @@ -225,6 +232,7 @@ class ApiQuery extends ApiBase { $this->params = $this->extractRequestParams(); $this->redirects = $this->params['redirects']; $this->convertTitles = $this->params['converttitles']; + $this->iwUrl = $this->params['iwurl']; // Create PageSet $this->mPageSet = new ApiPageSet( $this, $this->redirects, $this->convertTitles ); @@ -272,6 +280,8 @@ class ApiQuery extends ApiBase { * Update a cache mode string, applying the cache mode of a new module to it. * The cache mode may increase in the level of privacy, but public modules * added to private data do not decrease the level of privacy. + * + * @return string */ protected function mergeCacheMode( $cacheMode, $modCacheMode ) { if ( $modCacheMode === 'anon-public-user-private' ) { @@ -307,9 +317,8 @@ class ApiQuery extends ApiBase { * @param $moduleList Array array(modulename => classname) */ private function instantiateModules( &$modules, $param, $moduleList ) { - $list = @$this->params[$param]; - if ( !is_null ( $list ) ) { - foreach ( $list as $moduleName ) { + if ( isset( $this->params[$param] ) ) { + foreach ( $this->params[$param] as $moduleName ) { $modules[] = new $moduleList[$moduleName] ( $this, $moduleName ); } } @@ -359,10 +368,15 @@ class ApiQuery extends ApiBase { // Interwiki titles $intrwValues = array(); foreach ( $pageSet->getInterwikiTitles() as $rawTitleStr => $interwikiStr ) { - $intrwValues[] = array( + $item = array( 'title' => $rawTitleStr, - 'iw' => $interwikiStr + 'iw' => $interwikiStr, ); + if ( $this->iwUrl ) { + $title = Title::newFromText( $rawTitleStr ); + $item['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); + } + $intrwValues[] = $item; } if ( count( $intrwValues ) ) { @@ -372,11 +386,15 @@ class ApiQuery extends ApiBase { // Show redirect information $redirValues = array(); - foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleStrTo ) { - $redirValues[] = array( + foreach ( $pageSet->getRedirectTitles() as $titleStrFrom => $titleTo ) { + $r = array( 'from' => strval( $titleStrFrom ), - 'to' => $titleStrTo + 'to' => $titleTo->getPrefixedText(), ); + if ( $titleTo->getFragment() !== '' ) { + $r['tofragment'] = $titleTo->getFragment(); + } + $redirValues[] = $r; } if ( count( $redirValues ) ) { @@ -424,7 +442,7 @@ class ApiQuery extends ApiBase { ApiQueryBase::addTitleInfo( $vals, $title ); $vals['special'] = ''; if ( $title->getNamespace() == NS_SPECIAL && - !SpecialPage::exists( $title->getDbKey() ) ) { + !SpecialPageFactory::exists( $title->getDbKey() ) ) { $vals['missing'] = ''; } elseif ( $title->getNamespace() == NS_MEDIA && !wfFindFile( $title ) ) { @@ -465,17 +483,13 @@ class ApiQuery extends ApiBase { private function doExport( $pageSet, $result ) { $exportTitles = array(); $titles = $pageSet->getGoodTitles(); - if( count( $titles ) ) { + if ( count( $titles ) ) { foreach ( $titles as $title ) { if ( $title->userCanRead() ) { $exportTitles[] = $title; } } } - // only export when there are titles - if ( !count( $exportTitles ) ) { - return; - } $exporter = new WikiExporter( $this->getDB() ); // WikiExporter writes to stdout, so catch its @@ -578,6 +592,7 @@ class ApiQuery extends ApiBase { 'indexpageids' => false, 'export' => false, 'exportnowrap' => false, + 'iwurl' => false, ); } @@ -586,16 +601,14 @@ class ApiQuery extends ApiBase { * @return string */ public function makeHelpMsg() { - $msg = ''; - // Make sure the internal object is empty // (just in case a sub-module decides to optimize during instantiation) $this->mPageSet = null; $this->mAllowedGenerators = array(); // Will be repopulated - $querySeparator = str_repeat( '--- ', 8 ); - $moduleSeparator = str_repeat( '*** ', 10 ); - $msg .= "\n$querySeparator Query: Prop $querySeparator\n\n"; + $querySeparator = str_repeat( '--- ', 12 ); + $moduleSeparator = str_repeat( '*** ', 14 ); + $msg = "\n$querySeparator Query: Prop $querySeparator\n\n"; $msg .= $this->makeHelpMsgHelper( $this->mQueryPropModules, 'prop' ); $msg .= "\n$querySeparator Query: List $querySeparator\n\n"; $msg .= $this->makeHelpMsgHelper( $this->mQueryListModules, 'list' ); @@ -621,7 +634,7 @@ class ApiQuery extends ApiBase { $moduleDescriptions = array(); foreach ( $moduleList as $moduleName => $moduleClass ) { - $module = new $moduleClass ( $this, $moduleName, null ); + $module = new $moduleClass( $this, $moduleName, null ); $msg = ApiMain::makeHelpMsgHeader( $module, $paramName ); $msg2 = $module->makeHelpMsg(); @@ -664,6 +677,7 @@ class ApiQuery extends ApiBase { 'indexpageids' => 'Include an additional pageids section listing all returned page IDs', 'export' => 'Export the current revisions of all given or generated pages', 'exportnowrap' => 'Return the export XML without wrapping it in an XML result (same format as Special:Export). Can only be used with export', + 'iwurl' => 'Whether to get the full URL if the title is an interwiki link', ); } @@ -688,10 +702,18 @@ class ApiQuery extends ApiBase { ); } + public function getHelpUrls() { + return array( + 'https://www.mediawiki.org/wiki/API:Meta', + 'https://www.mediawiki.org/wiki/API:Properties', + 'https://www.mediawiki.org/wiki/API:Lists', + ); + } + public function getVersion() { $psModule = new ApiPageSet( $this ); $vers = array(); - $vers[] = __CLASS__ . ': $Id: ApiQuery.php 80897 2011-01-24 18:57:42Z catrope $'; + $vers[] = __CLASS__ . ': $Id: ApiQuery.php 104449 2011-11-28 15:52:04Z reedy $'; $vers[] = $psModule->getVersion(); return $vers; } diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php index c1473252..c7f4b0aa 100644 --- a/includes/api/ApiQueryAllCategories.php +++ b/includes/api/ApiQueryAllCategories.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,6 +53,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $db = $this->getDB(); $params = $this->extractRequestParams(); @@ -65,6 +69,10 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); $this->addWhereRange( 'cat_title', $dir, $from, $to ); + $min = $params['min']; + $max = $params['max']; + $this->addWhereRange( 'cat_pages', $dir, $min, $max ); + if ( isset( $params['prefix'] ) ) { $this->addWhere( 'cat_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } @@ -144,6 +152,14 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { 'descending' ), ), + 'min' => array( + ApiBase::PARAM_DFLT => null, + ApiBase::PARAM_TYPE => 'integer' + ), + 'max' => array( + ApiBase::PARAM_DFLT => null, + ApiBase::PARAM_TYPE => 'integer' + ), 'limit' => array( ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', @@ -165,6 +181,8 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { 'to' => 'The category to stop enumerating at', 'prefix' => 'Search for all category titles that begin with this value', 'dir' => 'Direction to sort in', + 'min' => 'Minimum number of category members', + 'max' => 'Maximum number of category members', 'limit' => 'How many categories to return', 'prop' => array( 'Which properties to get', @@ -185,7 +203,11 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Allcategories'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllCategories.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryAllCategories.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index 78784845..90620e91 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $db = $this->getDB(); $params = $this->extractRequestParams(); @@ -90,27 +94,22 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { ); } - if ( !is_null( $params['from'] ) ) { - $this->addWhere( 'pl_title>=' . $db->addQuotes( $this->titlePartToKey( $params['from'] ) ) ); - } - if ( !is_null( $params['to'] ) ) { - $this->addWhere( 'pl_title<=' . $db->addQuotes( $this->titlePartToKey( $params['to'] ) ) ); - } + $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); + $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); + $this->addWhereRange( 'pl_title', 'newer', $from, $to ); + if ( isset( $params['prefix'] ) ) { $this->addWhere( 'pl_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } - $this->addFields( array( - 'pl_title', - ) ); + $this->addFields( 'pl_title' ); $this->addFieldsIf( 'pl_from', !$params['unique'] ); $this->addOption( 'USE INDEX', 'pl_namespace' ); $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); - if ( $params['unique'] ) { - $this->addOption( 'ORDER BY', 'pl_title' ); - } else { + + if ( !$params['unique'] ) { $this->addOption( 'ORDER BY', 'pl_title, pl_from' ); } @@ -228,7 +227,11 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Alllinks'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllLinks.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiQueryAllLinks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index 77f507fc..c0984d95 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -1,6 +1,6 @@ addTables( 'user', 'u1' ); + + $this->addTables( 'user' ); $useIndex = true; - if ( !is_null( $params['from'] ) ) { - $this->addWhere( 'u1.user_name >= ' . $db->addQuotes( $this->keyToTitle( $params['from'] ) ) ); + $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' ); + $from = is_null( $params['from'] ) ? null : $this->keyToTitle( $params['from'] ); + $to = is_null( $params['to'] ) ? null : $this->keyToTitle( $params['to'] ); + + $this->addWhereRange( 'user_name', $dir, $from, $to ); + + if ( !is_null( $params['prefix'] ) ) { + $this->addWhere( 'user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) ); } - if ( !is_null( $params['to'] ) ) { - $this->addWhere( 'u1.user_name <= ' . $db->addQuotes( $this->keyToTitle( $params['to'] ) ) ); + + if ( !is_null( $params['rights'] ) ) { + $groups = array(); + foreach( $params['rights'] as $r ) { + $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) ); + } + + $groups = array_unique( $groups ); + + if ( is_null( $params['group'] ) ) { + $params['group'] = $groups; + } else { + $params['group'] = array_unique( array_merge( $params['group'], $groups ) ); + } } - if ( !is_null( $params['prefix'] ) ) { - $this->addWhere( 'u1.user_name' . $db->buildLike( $this->keyToTitle( $params['prefix'] ), $db->anyString() ) ); + if ( !is_null( $params['group'] ) && !is_null( $params['excludegroup'] ) ) { + $this->dieUsage( 'group and excludegroup cannot be used together', 'group-excludegroup' ); } - if ( !is_null( $params['group'] ) ) { + if ( !is_null( $params['group'] ) && count( $params['group'] ) ) { $useIndex = false; // Filter only users that belong to a given group $this->addTables( 'user_groups', 'ug1' ); - $ug1 = $this->getAliasedName( 'user_groups', 'ug1' ); - $this->addJoinConds( array( $ug1 => array( 'INNER JOIN', array( 'ug1.ug_user=u1.user_id', + $this->addJoinConds( array( 'ug1' => array( 'INNER JOIN', array( 'ug1.ug_user=user_id', 'ug1.ug_group' => $params['group'] ) ) ) ); } + if ( !is_null( $params['excludegroup'] ) && count( $params['excludegroup'] ) ) { + $useIndex = false; + // Filter only users don't belong to a given group + $this->addTables( 'user_groups', 'ug1' ); + + if ( count( $params['excludegroup'] ) == 1 ) { + $exclude = array( 'ug1.ug_group' => $params['excludegroup'][0] ); + } else { + $exclude = array( $db->makeList( array( 'ug1.ug_group' => $params['excludegroup'] ), LIST_OR ) ); + } + $this->addJoinConds( array( 'ug1' => array( 'LEFT OUTER JOIN', + array_merge( array( 'ug1.ug_user=user_id' ), $exclude ) + ) + ) ); + $this->addWhere( 'ug1.ug_user IS NULL' ); + } + if ( $params['witheditsonly'] ) { - $this->addWhere( 'u1.user_editcount > 0' ); + $this->addWhere( 'user_editcount > 0' ); } - if ( $fld_groups ) { + $this->showHiddenUsersAddBlockInfo( $fld_blockinfo ); + + if ( $fld_groups || $fld_rights ) { // Show the groups the given users belong to // request more than needed to avoid not getting all rows that belong to one user $groupCount = count( User::getAllGroups() ); $sqlLimit = $limit + $groupCount + 1; $this->addTables( 'user_groups', 'ug2' ); - $tname = $this->getAliasedName( 'user_groups', 'ug2' ); - $this->addJoinConds( array( $tname => array( 'LEFT JOIN', 'ug2.ug_user=u1.user_id' ) ) ); + $this->addJoinConds( array( 'ug2' => array( 'LEFT JOIN', 'ug2.ug_user=user_id' ) ) ); $this->addFields( 'ug2.ug_group ug_group2' ); } else { $sqlLimit = $limit + 1; } - $this->showHiddenUsersAddBlockInfo( $fld_blockinfo ); + + if ( $params['activeusers'] ) { + global $wgActiveUserDays; + $this->addTables( 'recentchanges' ); + + $this->addJoinConds( array( 'recentchanges' => array( + 'INNER JOIN', 'rc_user_text=user_name' + ) ) ); + + $this->addFields( 'COUNT(*) AS recentedits' ); + + $this->addWhere( "rc_log_type IS NULL OR rc_log_type != 'newusers'" ); + $timestamp = $db->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 ); + $this->addWhere( "rc_timestamp >= {$db->addQuotes( $timestamp )}" ); + + $this->addOption( 'GROUP BY', 'user_name' ); + } $this->addOption( 'LIMIT', $sqlLimit ); $this->addFields( array( - 'u1.user_name', - 'u1.user_id' + 'user_name', + 'user_id' ) ); - $this->addFieldsIf( 'u1.user_editcount', $fld_editcount ); - $this->addFieldsIf( 'u1.user_registration', $fld_registration ); + $this->addFieldsIf( 'user_editcount', $fld_editcount ); + $this->addFieldsIf( 'user_registration', $fld_registration ); - $this->addOption( 'ORDER BY', 'u1.user_name' ); if ( $useIndex ) { - $u1 = $this->getAliasedName( 'user', 'u1' ); - $this->addOption( 'USE INDEX', array( $u1 => 'user_name' ) ); + $this->addOption( 'USE INDEX', array( 'user' => 'user_name' ) ); } $res = $this->select( __METHOD__ ); @@ -153,12 +205,13 @@ class ApiQueryAllUsers extends ApiQueryBase { // Record new user's data $lastUser = $row->user_name; $lastUserData = array( - 'name' => $lastUser, 'userid' => $row->user_id, + 'name' => $lastUser, ); - if ( $fld_blockinfo && !is_null( $row->blocker_name ) ) { - $lastUserData['blockedby'] = $row->blocker_name; + if ( $fld_blockinfo && !is_null( $row->ipb_by_text ) ) { + $lastUserData['blockedby'] = $row->ipb_by_text; $lastUserData['blockreason'] = $row->ipb_reason; + $lastUserData['blockexpiry'] = $row->ipb_expiry; } if ( $row->ipb_deleted ) { $lastUserData['hidden'] = ''; @@ -166,11 +219,13 @@ class ApiQueryAllUsers extends ApiQueryBase { if ( $fld_editcount ) { $lastUserData['editcount'] = intval( $row->user_editcount ); } + if ( $params['activeusers'] ) { + $lastUserData['recenteditcount'] = intval( $row->recentedits ); + } if ( $fld_registration ) { $lastUserData['registration'] = $row->user_registration ? wfTimestamp( TS_ISO_8601, $row->user_registration ) : ''; } - } if ( $sqlLimit == $count ) { @@ -181,10 +236,31 @@ class ApiQueryAllUsers extends ApiQueryBase { } // Add user's group info - if ( $fld_groups && !is_null( $row->ug_group2 ) ) { - $lastUserData['groups'][] = $row->ug_group2; + if ( $fld_groups ) { + if ( !isset( $lastUserData['groups'] ) ) { + $lastUserData['groups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) ); + } + + if ( !is_null( $row->ug_group2 ) ) { + $lastUserData['groups'][] = $row->ug_group2; + } $result->setIndexedTagName( $lastUserData['groups'], 'g' ); } + + if ( $fld_implicitgroups && !isset( $lastUserData['implicitgroups'] ) ) { + $lastUserData['implicitgroups'] = ApiQueryUsers::getAutoGroups( User::newFromName( $lastUser ) ); + $result->setIndexedTagName( $lastUserData['implicitgroups'], 'g' ); + } + if ( $fld_rights ) { + if ( !isset( $lastUserData['rights'] ) ) { + $lastUserData['rights'] = User::getGroupPermissions( User::newFromName( $lastUser )->getAutomaticGroups() ); + } + if ( !is_null( $row->ug_group2 ) ) { + $lastUserData['rights'] = array_unique( array_merge( $lastUserData['rights'], + User::getGroupPermissions( array( $row->ug_group2 ) ) ) ); + } + $result->setIndexedTagName( $lastUserData['rights'], 'r' ); + } } if ( is_array( $lastUserData ) ) { @@ -204,18 +280,37 @@ class ApiQueryAllUsers extends ApiQueryBase { } public function getAllowedParams() { + $userGroups = User::getAllGroups(); return array( 'from' => null, 'to' => null, 'prefix' => null, + 'dir' => array( + ApiBase::PARAM_DFLT => 'ascending', + ApiBase::PARAM_TYPE => array( + 'ascending', + 'descending' + ), + ), 'group' => array( - ApiBase::PARAM_TYPE => User::getAllGroups() + ApiBase::PARAM_TYPE => $userGroups, + ApiBase::PARAM_ISMULTI => true, + ), + 'excludegroup' => array( + ApiBase::PARAM_TYPE => $userGroups, + ApiBase::PARAM_ISMULTI => true, + ), + 'rights' => array( + ApiBase::PARAM_TYPE => User::getAllRights(), + ApiBase::PARAM_ISMULTI => true, ), 'prop' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => array( 'blockinfo', 'groups', + 'implicitgroups', + 'rights', 'editcount', 'registration' ) @@ -228,24 +323,32 @@ class ApiQueryAllUsers extends ApiQueryBase { ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), 'witheditsonly' => false, + 'activeusers' => false, ); } public function getParamDescription() { + global $wgActiveUserDays; return array( 'from' => 'The user name to start enumerating from', 'to' => 'The user name to stop enumerating at', 'prefix' => 'Search for all users that begin with this value', - 'group' => 'Limit users to a given group name', + 'dir' => 'Direction to sort in', + 'group' => 'Limit users to given group name(s)', + 'excludegroup' => 'Exclude users in given group name(s)', + 'rights' => 'Limit users to given right(s)', 'prop' => array( 'What pieces of information to include.', - ' blockinfo - Adds the information about a current block on the user', - ' groups - Lists groups that the user is in', - ' editcount - Adds the edit count of the user', - ' registration - Adds the timestamp of when the user registered', - '`groups` property uses more server resources and may return fewer results than the limit' ), + ' blockinfo - Adds the information about a current block on the user', + ' groups - Lists groups that the user is in. This uses more server resources and may return fewer results than the limit', + ' implicitgroups - Lists all the groups the user is automatically in', + ' rights - Lists rights that the user has', + ' editcount - Adds the edit count of the user', + ' registration - Adds the timestamp of when the user registered if available (may be blank)', + ), 'limit' => 'How many total user names to return', 'witheditsonly' => 'Only list users who have made edits', + 'activeusers' => "Only list users active in the last {$wgActiveUserDays} days(s)" ); } @@ -253,13 +356,23 @@ class ApiQueryAllUsers extends ApiQueryBase { return 'Enumerate all registered users'; } + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'code' => 'group-excludegroup', 'info' => 'group and excludegroup cannot be used together' ), + ) ); + } + protected function getExamples() { return array( 'api.php?action=query&list=allusers&aufrom=Y', ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Allusers'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllUsers.php 85354 2011-04-04 18:25:31Z demon $'; + return __CLASS__ . ': $Id: ApiQueryAllUsers.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryAllimages.php b/includes/api/ApiQueryAllimages.php index a7825519..cafff871 100644 --- a/includes/api/ApiQueryAllimages.php +++ b/includes/api/ApiQueryAllimages.php @@ -38,16 +38,19 @@ if ( !defined( 'MEDIAWIKI' ) ) { */ class ApiQueryAllimages extends ApiQueryGeneratorBase { + protected $mRepo; + public function __construct( $query, $moduleName ) { parent::__construct( $query, $moduleName, 'ai' ); $this->mRepo = RepoGroup::singleton()->getLocalRepo(); } /** - * Overide parent method to make sure to make sure the repo's DB is used + * Override parent method to make sure to make sure the repo's DB is used * which may not necesarilly be the same as the local DB. * * TODO: allow querying non-local repos. + * @return DatabaseBase */ protected function getDB() { return $this->mRepo->getSlaveDB(); @@ -61,6 +64,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { return 'public'; } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ public function executeGenerator( $resultPageSet ) { if ( $resultPageSet->isResolvingRedirects() ) { $this->dieUsage( 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator', 'params' ); @@ -69,6 +76,10 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $repo = $this->mRepo; if ( !$repo instanceof LocalRepo ) { @@ -98,12 +109,30 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $sha1 = false; if ( isset( $params['sha1'] ) ) { + if ( !$this->validateSha1Hash( $params['sha1'] ) ) { + $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' ); + } $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 ); } elseif ( isset( $params['sha1base36'] ) ) { $sha1 = $params['sha1base36']; + if ( !$this->validateSha1Base36Hash( $sha1 ) ) { + $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' ); + } } if ( $sha1 ) { - $this->addWhere( 'img_sha1=' . $db->addQuotes( $sha1 ) ); + $this->addWhereFld( 'img_sha1', $sha1 ); + } + + if ( !is_null( $params['mime'] ) ) { + global $wgMiserMode; + if ( $wgMiserMode ) { + $this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' ); + } + + list( $major, $minor ) = File::splitMime( $params['mime'] ); + + $this->addWhereFld( 'img_major_mime', $major ); + $this->addWhereFld( 'img_minor_mime', $minor ); } $this->addTables( 'image' ); @@ -133,6 +162,8 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { $file = $repo->newFileFromRow( $row ); $info = array_merge( array( 'name' => $row->img_name ), ApiQueryImageInfo::getInfo( $file, $prop, $result ) ); + self::addTitleInfo( $info, $file->getTitle() ); + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $info ); if ( !$fit ) { $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->img_name ) ); @@ -178,10 +209,11 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { 'sha1' => null, 'sha1base36' => null, 'prop' => array( - ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames(), + ApiBase::PARAM_TYPE => ApiQueryImageInfo::getPropertyNames( $this->propertyFilter ), ApiBase::PARAM_DFLT => 'timestamp|url', ApiBase::PARAM_ISMULTI => true - ) + ), + 'mime' => null, ); } @@ -196,24 +228,13 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { 'limit' => 'How many images in total to return', 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36", 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki)', - 'prop' => array( - 'Which properties to get', - ' timestamp - Adds the timestamp when the image was upload', - ' user - Adds the username of the last uploader', - ' userid - Adds the user id of the last uploader', - ' comment - Adds the comment of the last upload', - ' url - Adds the URL of the image and its description page', - ' size - Adds the size of the image in bytes and its height and width', - ' dimensions - Alias of size', - ' sha1 - Adds the sha1 of the image', - ' mime - Adds the MIME of the image', - ' thumbmime - Adds the MIME of the tumbnail for the image', - ' archivename - Adds the file name of the archive version for non-latest versions', - ' bitdepth - Adds the bit depth of the version', - ), + 'prop' => ApiQueryImageInfo::getPropertyDescriptions( $this->propertyFilter ), + 'mime' => 'What MIME type to search for. e.g. image/jpeg. Disabled in Miser Mode', ); } + private $propertyFilter = array( 'archivename' ); + public function getDescription() { return 'Enumerate all images sequentially'; } @@ -222,6 +243,9 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'params', 'info' => 'Use "gaifilterredir=nonredirects" option instead of "redirects" when using allimages as a generator' ), array( 'code' => 'unsupportedrepo', 'info' => 'Local file repository does not support querying all images' ), + array( 'code' => 'mimesearchdisabled', 'info' => 'MIME search disabled in Miser Mode' ), + array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ), + array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ), ) ); } @@ -236,7 +260,11 @@ class ApiQueryAllimages extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Allimages'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllimages.php 71838 2010-08-28 01:18:18Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryAllimages.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryAllmessages.php b/includes/api/ApiQueryAllmessages.php index 81ff255a..a26011bc 100644 --- a/includes/api/ApiQueryAllmessages.php +++ b/includes/api/ApiQueryAllmessages.php @@ -1,6 +1,6 @@ extractRequestParams(); - global $wgLang; + if ( is_null( $params['lang'] ) ) { + global $wgLang; + $langObj = $wgLang; + } else { + $langObj = Language::factory( $params['lang'] ); + } - $oldLang = null; - if ( !is_null( $params['lang'] ) ) { - $oldLang = $wgLang; // Keep $wgLang for restore later - $wgLang = Language::factory( $params['lang'] ); + if ( $params['enableparser'] ) { + if ( !is_null( $params['title'] ) ) { + $title = Title::newFromText( $params['title'] ); + if ( !$title ) { + $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); + } + } else { + $title = Title::newFromText( 'API' ); + } } $prop = array_flip( (array)$params['prop'] ); @@ -62,7 +72,26 @@ class ApiQueryAllmessages extends ApiQueryBase { $messages_target = $params['messages']; } - // Filter messages + // Filter messages that have the specified prefix + // Because we sorted the message array earlier, they will appear in a clump: + if ( isset( $params['prefix'] ) ) { + $skip = false; + $messages_filtered = array(); + foreach ( $messages_target as $message ) { + // === 0: must be at beginning of string (position 0) + if ( strpos( $message, $params['prefix'] ) === 0 ) { + if( !$skip ) { + $skip = true; + } + $messages_filtered[] = $message; + } elseif ( $skip ) { + break; + } + } + $messages_target = $messages_filtered; + } + + // Filter messages that contain specified string if ( isset( $params['filter'] ) ) { $messages_filtered = array(); foreach ( $messages_target as $message ) { @@ -74,6 +103,18 @@ class ApiQueryAllmessages extends ApiQueryBase { $messages_target = $messages_filtered; } + // Whether we have any sort of message customisation filtering + $customiseFilterEnabled = $params['customised'] !== 'all'; + if ( $customiseFilterEnabled ) { + global $wgContLang; + $lang = $langObj->getCode(); + + $customisedMessages = AllmessagesTablePager::getCustomisedStatuses( + array_map( array( $langObj, 'ucfirst'), $messages_target ), $lang, $lang != $wgContLang->getCode() ); + + $customised = $params['customised'] === 'modified'; + } + // Get all requested messages and print the result $skip = !is_null( $params['from'] ); $useto = !is_null( $params['to'] ); @@ -83,39 +124,47 @@ class ApiQueryAllmessages extends ApiQueryBase { if ( $skip && $message === $params['from'] ) { $skip = false; } - - if( $useto && $message > $params['to'] ) { + + if ( $useto && $message > $params['to'] ) { break; } if ( !$skip ) { $a = array( 'name' => $message ); - $args = null; + $args = array(); if ( isset( $params['args'] ) && count( $params['args'] ) != 0 ) { $args = $params['args']; } - // Check if the parser is enabled: - if ( $params['enableparser'] ) { - $msg = wfMsgExt( $message, array( 'parsemag' ), $args ); - } elseif ( $args ) { - $msgString = wfMsgGetKey( $message, true, false, false ); - $msg = wfMsgReplaceArgs( $msgString, $args ); - } else { - $msg = wfMsgGetKey( $message, true, false, false ); + + if ( $customiseFilterEnabled ) { + $messageIsCustomised = isset( $customisedMessages['pages'][ $langObj->ucfirst( $message ) ] ); + if ( $customised === $messageIsCustomised ) { + if ( $customised ) { + $a['customised'] = ''; + } + } else { + continue; + } } - if ( wfEmptyMsg( $message, $msg ) ) { + $msg = wfMessage( $message, $args )->inLanguage( $langObj ); + + if ( !$msg->exists() ) { $a['missing'] = ''; } else { - ApiResult::setContent( $a, $msg ); + // Check if the parser is enabled: + if ( $params['enableparser'] ) { + $msgString = $msg->title( $title )->text(); + } else { + $msgString = $msg->plain(); + } + ApiResult::setContent( $a, $msgString ); if ( isset( $prop['default'] ) ) { - $default = wfMsgGetKey( $message, false, false, false ); - if ( $default !== $msg ) { - if ( wfEmptyMsg( $message, $default ) ) { - $a['defaultmissing'] = ''; - } else { - $a['default'] = $default; - } + $default = wfMessage( $message )->inLanguage( $langObj )->useDatabase( false ); + if ( !$default->exists() ) { + $a['defaultmissing'] = ''; + } elseif ( $default->plain() != $msgString ) { + $a['default'] = $default->plain(); } } } @@ -127,10 +176,6 @@ class ApiQueryAllmessages extends ApiQueryBase { } } $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'message' ); - - if ( !is_null( $oldLang ) ) { - $wgLang = $oldLang; // Restore $oldLang - } } public function getCacheMode( $params ) { @@ -160,23 +205,37 @@ class ApiQueryAllmessages extends ApiQueryBase { ), 'enableparser' => false, 'args' => array( - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_ALLOW_DUPLICATES => true, ), 'filter' => array(), + 'customised' => array( + ApiBase::PARAM_DFLT => 'all', + ApiBase::PARAM_TYPE => array( + 'all', + 'modified', + 'unmodified' + ) + ), 'lang' => null, 'from' => null, 'to' => null, + 'title' => null, + 'prefix' => null, ); } public function getParamDescription() { return array( - 'messages' => 'Which messages to output. "*" means all messages', + 'messages' => 'Which messages to output. "*" (default) means all messages', 'prop' => 'Which properties to get', 'enableparser' => array( 'Set to enable parser, will preprocess the wikitext of message', 'Will substitute magic words, handle templates etc.' ), + 'title' => 'Page name to use as context when parsing message (for enableparser option)', 'args' => 'Arguments to be substituted into message', - 'filter' => 'Return only messages that contain this string', + 'prefix' => 'Return messages with this prefix', + 'filter' => 'Return only messages with names that contain this string', + 'customised' => 'Return only messages in this customisation state', 'lang' => 'Return messages in this language', 'from' => 'Return messages starting at this message', 'to' => 'Return messages ending at this message', @@ -189,12 +248,16 @@ class ApiQueryAllmessages extends ApiQueryBase { protected function getExamples() { return array( - 'api.php?action=query&meta=allmessages&amfilter=ipb-', + 'api.php?action=query&meta=allmessages&refix=ipb-', 'api.php?action=query&meta=allmessages&ammessages=august|mainpage&amlang=de', ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Meta#allmessages_.2F_am'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllmessages.php 73756 2010-09-25 17:08:23Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryAllmessages.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryAllpages.php b/includes/api/ApiQueryAllpages.php index 21f72916..42928418 100644 --- a/includes/api/ApiQueryAllpages.php +++ b/includes/api/ApiQueryAllpages.php @@ -1,6 +1,6 @@ isResolvingRedirects() ) { $this->dieUsage( 'Use "gapfilterredir=nonredirects" option instead of "redirects" when using allpages as a generator', 'params' ); @@ -56,6 +60,10 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $db = $this->getDB(); @@ -75,7 +83,7 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); $this->addWhereRange( 'page_title', $dir, $from, $to ); - + if ( isset( $params['prefix'] ) ) { $this->addWhere( 'page_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } @@ -103,30 +111,38 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { } // Page protection filtering - if ( !empty( $params['prtype'] ) ) { + if ( count( $params['prtype'] ) || $params['prexpiry'] != 'all' ) { $this->addTables( 'page_restrictions' ); $this->addWhere( 'page_id=pr_page' ); $this->addWhere( 'pr_expiry>' . $db->addQuotes( $db->timestamp() ) ); - $this->addWhereFld( 'pr_type', $params['prtype'] ); - if ( isset( $params['prlevel'] ) ) { - // Remove the empty string and '*' from the prlevel array - $prlevel = array_diff( $params['prlevel'], array( '', '*' ) ); + if ( count( $params['prtype'] ) ) { + $this->addWhereFld( 'pr_type', $params['prtype'] ); - if ( !empty( $prlevel ) ) { - $this->addWhereFld( 'pr_level', $prlevel ); - } - } - if ( $params['prfiltercascade'] == 'cascading' ) { - $this->addWhereFld( 'pr_cascade', 1 ); - } elseif ( $params['prfiltercascade'] == 'noncascading' ) { - $this->addWhereFld( 'pr_cascade', 0 ); - } + if ( isset( $params['prlevel'] ) ) { + // Remove the empty string and '*' from the prlevel array + $prlevel = array_diff( $params['prlevel'], array( '', '*' ) ); - $this->addOption( 'DISTINCT' ); + if ( count( $prlevel ) ) { + $this->addWhereFld( 'pr_level', $prlevel ); + } + } + if ( $params['prfiltercascade'] == 'cascading' ) { + $this->addWhereFld( 'pr_cascade', 1 ); + } elseif ( $params['prfiltercascade'] == 'noncascading' ) { + $this->addWhereFld( 'pr_cascade', 0 ); + } + $this->addOption( 'DISTINCT' ); + } $forceNameTitleIndex = false; + if ( $params['prexpiry'] == 'indefinite' ) { + $this->addWhere( "pr_expiry = {$db->addQuotes( $db->getInfinity() )} OR pr_expiry IS NULL" ); + } elseif ( $params['prexpiry'] == 'definite' ) { + $this->addWhere( "pr_expiry != {$db->addQuotes( $db->getInfinity() )}" ); + } + } elseif ( isset( $params['prlevel'] ) ) { $this->dieUsage( 'prlevel may not be used without prtype', 'params' ); } @@ -248,7 +264,15 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { 'all' ), ApiBase::PARAM_DFLT => 'all' - ) + ), + 'prexpiry' => array( + ApiBase::PARAM_TYPE => array( + 'indefinite', + 'definite', + 'all' + ), + ApiBase::PARAM_DFLT => 'all' + ), ); } @@ -267,7 +291,13 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { 'prlevel' => "The protection level (must be used with {$p}prtype= parameter)", 'prfiltercascade' => "Filter protections based on cascadingness (ignored when {$p}prtype isn't set)", 'filterlanglinks' => 'Filter based on whether a page has langlinks', - 'limit' => 'How many total pages to return.' + 'limit' => 'How many total pages to return.', + 'prexpiry' => array( + 'Which protection expiry to filter the page on', + ' indefinite - Get only pages with indefinite protection expiry', + ' definite - Get only pages with a definite (specific) protection expiry', + ' all - Get pages with any protections expiry' + ), ); } @@ -295,7 +325,11 @@ class ApiQueryAllpages extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Allpages'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryAllpages.php 85354 2011-04-04 18:25:31Z demon $'; + return __CLASS__ . ': $Id: ApiQueryAllpages.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index b412d2d6..472406ac 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -1,6 +1,6 @@ array( 'code' => 'bl', 'prefix' => 'pl', - 'linktbl' => 'pagelinks' + 'linktbl' => 'pagelinks', + 'helpurl' => 'https://www.mediawiki.org/wiki/API:Backlinks', ), 'embeddedin' => array( 'code' => 'ei', 'prefix' => 'tl', - 'linktbl' => 'templatelinks' + 'linktbl' => 'templatelinks', + 'helpurl' => 'https://www.mediawiki.org/wiki/API:Embeddedin', ), 'imageusage' => array( 'code' => 'iu', 'prefix' => 'il', - 'linktbl' => 'imagelinks' + 'linktbl' => 'imagelinks', + 'helpurl' => 'https://www.mediawiki.org/wiki/API:Imageusage', ) ); @@ -73,6 +91,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->bl_from = $prefix . '_from'; $this->bl_table = $settings['linktbl']; $this->bl_code = $code; + $this->helpUrl = $settings['helpurl']; $this->hasNS = $moduleName !== 'imageusage'; if ( $this->hasNS ) { @@ -103,6 +122,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function prepareFirstQuery( $resultPageSet = null ) { /* SELECT page_id, page_title, page_namespace, page_is_redirect * FROM pagelinks, page WHERE pl_from=page_id @@ -141,6 +164,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->addOption( 'STRAIGHT_JOIN' ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function prepareSecondQuery( $resultPageSet = null ) { /* SELECT page_id, page_title, page_namespace, page_is_redirect, pl_title, pl_namespace FROM pagelinks, page WHERE pl_from=page_id @@ -199,14 +226,21 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->addOption( 'USE INDEX', array( 'page' => 'PRIMARY' ) ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $this->params = $this->extractRequestParams( false ); $this->redirect = isset( $this->params['redirect'] ) && $this->params['redirect']; $userMax = ( $this->redirect ? ApiBase::LIMIT_BIG1 / 2 : ApiBase::LIMIT_BIG1 ); $botMax = ( $this->redirect ? ApiBase::LIMIT_BIG2 / 2 : ApiBase::LIMIT_BIG2 ); + + $result = $this->getResult(); + if ( $this->params['limit'] == 'max' ) { $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; - $this->getResult()->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); + $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); } $this->processContinue(); @@ -215,9 +249,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $res = $this->select( __METHOD__ . '::firstQuery' ); $count = 0; - $this->pageMap = array(); // Maps ns and title to pageid - $this->continueStr = null; - $this->redirTitles = array(); + foreach ( $res as $row ) { if ( ++ $count > $this->params['limit'] ) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -248,9 +280,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { // We've reached the one extra which shows that there are additional pages to be had. Stop here... // We need to keep the parent page of this redir in if ( $this->hasNS ) { - $parentID = $this->pageMap[$row->{$this->bl_ns}][$row->{$this->bl_title}]; + $parentID = $this->pageMap[$row-> { $this->bl_ns } ][$row-> { $this->bl_title } ]; } else { - $parentID = $this->pageMap[NS_IMAGE][$row->{$this->bl_title}]; + $parentID = $this->pageMap[NS_IMAGE][$row-> { $this->bl_title } ]; } $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id ); break; @@ -265,13 +297,13 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } if ( is_null( $resultPageSet ) ) { // Try to add the result data in one go and pray that it fits - $fit = $this->getResult()->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) ); + $fit = $result->addValue( 'query', $this->getModuleName(), array_values( $this->resultArr ) ); if ( !$fit ) { // It didn't fit. Add elements one by one until the // result is full. foreach ( $this->resultArr as $pageID => $arr ) { // Add the basic entry without redirlinks first - $fit = $this->getResult()->addValue( + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, array_diff_key( $arr, array( 'redirlinks' => '' ) ) ); if ( !$fit ) { @@ -280,8 +312,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } $hasRedirs = false; - foreach ( (array)@$arr['redirlinks'] as $key => $redir ) { - $fit = $this->getResult()->addValue( + $redirLinks = isset( $arr['redirlinks'] ) ? $arr['redirlinks'] : array(); + foreach ( (array)$redirLinks as $key => $redir ) { + $fit = $result->addValue( array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), $key, $redir ); if ( !$fit ) { @@ -291,7 +324,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $hasRedirs = true; } if ( $hasRedirs ) { - $this->getResult()->setIndexedTagName_internal( + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName(), $pageID, 'redirlinks' ), $this->bl_code ); } @@ -301,7 +334,7 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { } } - $this->getResult()->setIndexedTagName_internal( + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $this->bl_code ); @@ -383,9 +416,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', '_badcontinue' ); } $this->contID = $contID; - $redirID = intval( @$continueList[3] ); + $id2 = isset( $continueList[3] ) ? $continueList[3] : null; + $redirID = intval( $id2 ); - if ( $redirID === 0 && @$continueList[3] !== '0' ) { + if ( $redirID === 0 && $id2 !== '0' ) { // This one isn't required return; } @@ -496,7 +530,11 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { return $examples[$this->getModuleName()]; } + public function getHelpUrls() { + return $this->helpUrl; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBacklinks.php 75921 2010-11-03 12:49:21Z demon $'; + return __CLASS__ . ': $Id: ApiQueryBacklinks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 61a5b4c8..69e0a893 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -1,6 +1,6 @@ tables = array_merge( $this->tables, $tables ); } else { if ( !is_null( $alias ) ) { - $tables = $this->getAliasedName( $tables, $alias ); + $this->tables[$alias] = $tables; + } else { + $this->tables[] = $tables; } - $this->tables[] = $tables; } } - /** - * Get the SQL for a table name with alias - * @param $table string Table name - * @param $alias string Alias - * @return string SQL - */ - protected function getAliasedName( $table, $alias ) { - return $this->getDB()->tableName( $table ) . ' ' . $alias; - } - /** * Add a set of JOIN conditions to the internal array * @@ -118,7 +111,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Add a set of fields to select to the internal array - * @param $value mixed Field name or array of field names + * @param $value array|string Field name or array of field names */ protected function addFields( $value ) { if ( is_array( $value ) ) { @@ -130,7 +123,7 @@ abstract class ApiQueryBase extends ApiBase { /** * Same as addFields(), but add the fields only if a condition is met - * @param $value mixed See addFields() + * @param $value array|string See addFields() * @param $condition bool If false, do nothing * @return bool $condition */ @@ -227,6 +220,16 @@ abstract class ApiQueryBase extends ApiBase { } } } + /** + * Add a WHERE clause corresponding to a range, similar to addWhereRange, + * but converts $start and $end to database timestamps. + * @see addWhereRange + */ + protected function addTimestampWhereRange( $field, $dir, $start, $end, $sort = true ) { + $db = $this->getDb(); + return $this->addWhereRange( $field, $dir, + $db->timestampOrNull( $start ), $db->timestampOrNull( $end ), $sort ); + } /** * Add an option such as LIMIT or USE INDEX. If an option was set @@ -359,14 +362,15 @@ abstract class ApiQueryBase extends ApiBase { protected function setContinueEnumParameter( $paramName, $paramValue ) { $paramName = $this->encodeParamName( $paramName ); $msg = array( $paramName => $paramValue ); - $this->getResult()->disableSizeCheck(); - $this->getResult()->addValue( 'query-continue', $this->getModuleName(), $msg ); - $this->getResult()->enableSizeCheck(); + $result = $this->getResult(); + $result->disableSizeCheck(); + $result->addValue( 'query-continue', $this->getModuleName(), $msg ); + $result->enableSizeCheck(); } /** * Get the Query database connection (read-only) - * @return Database + * @return DatabaseBase */ protected function getDB() { if ( is_null( $this->mDb ) ) { @@ -449,6 +453,47 @@ abstract class ApiQueryBase extends ApiBase { return substr( $this->keyToTitle( $keyPart . 'x' ), 0, - 1 ); } + /** + * Gets the personalised direction parameter description + * + * @param string $p ModulePrefix + * @param string $extraDirText Any extra text to be appended on the description + * @return array + */ + public function getDirectionDescription( $p = '', $extraDirText = '' ) { + return array( + "In which direction to enumerate{$extraDirText}", + " newer - List oldest first. Note: {$p}start has to be before {$p}end.", + " older - List newest first (default). Note: {$p}start has to be later than {$p}end.", + ); + } + + /** + * @param $query String + * @param $protocol String + * @return null|string + */ + public function prepareUrlQuerySearchString( $query = null, $protocol = null) { + $db = $this->getDb(); + if ( !is_null( $query ) || $query != '' ) { + if ( is_null( $protocol ) ) { + $protocol = 'http://'; + } + + $likeQuery = LinkFilter::makeLikeArray( $query, $protocol ); + if ( !$likeQuery ) { + $this->dieUsage( 'Invalid query', 'bad_query' ); + } + + $likeQuery = LinkFilter::keepOneWildcard( $likeQuery ); + return 'el_index ' . $db->buildLike( $likeQuery ); + } elseif ( !is_null( $protocol ) ) { + return 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ); + } + + return null; + } + /** * Filters hidden users (where the user doesn't have the right to view them) * Also adds relevant block information @@ -479,6 +524,25 @@ abstract class ApiQueryBase extends ApiBase { } } + /** + * @param $hash string + * @return bool + */ + public function validateSha1Hash( $hash ) { + return preg_match( '/[a-fA-F0-9]{40}/', $hash ); + } + + /** + * @param $hash string + * @return bool + */ + public function validateSha1Base36Hash( $hash ) { + return preg_match( '/[a-zA-Z0-9]{31}/', $hash ); + } + + /** + * @return array + */ public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( array( 'invalidtitle', 'title' ), @@ -491,7 +555,7 @@ abstract class ApiQueryBase extends ApiBase { * @return string */ public static function getBaseVersion() { - return __CLASS__ . ': $Id: ApiQueryBase.php 85435 2011-04-05 14:00:08Z demon $'; + return __CLASS__ . ': $Id: ApiQueryBase.php 103029 2011-11-14 20:58:30Z reedy $'; } } diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index 4edda645..503af7c7 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -30,30 +30,33 @@ if ( !defined( 'MEDIAWIKI' ) ) { } /** - * Query module to enumerate all available pages. + * Query module to enumerate all user blocks * * @ingroup API */ class ApiQueryBlocks extends ApiQueryBase { - var $users; + /** + * @var Array + */ + protected $usernames; public function __construct( $query, $moduleName ) { parent::__construct( $query, $moduleName, 'bk' ); } public function execute() { - global $wgUser; + global $wgUser, $wgContLang; $params = $this->extractRequestParams(); - if ( isset( $params['users'] ) && isset( $params['ip'] ) ) { - $this->dieUsage( 'bkusers and bkip cannot be used together', 'usersandip' ); - } + $this->requireMaxOneParameter( $params, 'users', 'ip' ); $prop = array_flip( $params['prop'] ); $fld_id = isset( $prop['id'] ); $fld_user = isset( $prop['user'] ); + $fld_userid = isset( $prop['userid'] ); $fld_by = isset( $prop['by'] ); + $fld_byid = isset( $prop['byid'] ); $fld_timestamp = isset( $prop['timestamp'] ); $fld_expiry = isset( $prop['expiry'] ); $fld_reason = isset( $prop['reason'] ); @@ -65,35 +68,20 @@ class ApiQueryBlocks extends ApiQueryBase { $this->addTables( 'ipblocks' ); $this->addFields( 'ipb_auto' ); - if ( $fld_id ) { - $this->addFields( 'ipb_id' ); - } - if ( $fld_user ) { - $this->addFields( array( 'ipb_address', 'ipb_user' ) ); - } - if ( $fld_by ) { - $this->addTables( 'user' ); - $this->addFields( array( 'ipb_by', 'user_name' ) ); - $this->addWhere( 'user_id = ipb_by' ); - } - if ( $fld_timestamp ) { - $this->addFields( 'ipb_timestamp' ); - } - if ( $fld_expiry ) { - $this->addFields( 'ipb_expiry' ); - } - if ( $fld_reason ) { - $this->addFields( 'ipb_reason' ); - } - if ( $fld_range ) { - $this->addFields( array( 'ipb_range_start', 'ipb_range_end' ) ); - } - if ( $fld_flags ) { - $this->addFields( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ) ); - } + $this->addFieldsIf ( 'ipb_id', $fld_id ); + $this->addFieldsIf( array( 'ipb_address', 'ipb_user' ), $fld_user || $fld_userid ); + $this->addFieldsIf( 'ipb_by_text', $fld_by ); + $this->addFieldsIf( 'ipb_by', $fld_byid ); + $this->addFieldsIf( 'ipb_timestamp', $fld_timestamp ); + $this->addFieldsIf( 'ipb_expiry', $fld_expiry ); + $this->addFieldsIf( 'ipb_reason', $fld_reason ); + $this->addFieldsIf( array( 'ipb_range_start', 'ipb_range_end' ), $fld_range ); + $this->addFieldsIf( array( 'ipb_anon_only', 'ipb_create_account', 'ipb_enable_autoblock', + 'ipb_block_email', 'ipb_deleted', 'ipb_allow_usertalk' ), + $fld_flags ); $this->addOption( 'LIMIT', $params['limit'] + 1 ); - $this->addWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] ); + $this->addTimestampWhereRange( 'ipb_timestamp', $params['dir'], $params['start'], $params['end'] ); if ( isset( $params['ids'] ) ) { $this->addWhereFld( 'ipb_id', $params['ids'] ); } @@ -151,14 +139,20 @@ class ApiQueryBlocks extends ApiQueryBase { if ( $fld_user && !$row->ipb_auto ) { $block['user'] = $row->ipb_address; } + if ( $fld_userid && !$row->ipb_auto ) { + $block['userid'] = $row->ipb_user; + } if ( $fld_by ) { - $block['by'] = $row->user_name; + $block['by'] = $row->ipb_by_text; + } + if ( $fld_byid ) { + $block['byid'] = $row->ipb_by; } if ( $fld_timestamp ) { $block['timestamp'] = wfTimestamp( TS_ISO_8601, $row->ipb_timestamp ); } if ( $fld_expiry ) { - $block['expiry'] = Block::decodeExpiry( $row->ipb_expiry, TS_ISO_8601 ); + $block['expiry'] = $wgContLang->formatExpiry( $row->ipb_expiry, TS_ISO_8601 ); } if ( $fld_reason ) { $block['reason'] = $row->ipb_reason; @@ -248,7 +242,9 @@ class ApiQueryBlocks extends ApiQueryBase { ApiBase::PARAM_TYPE => array( 'id', 'user', + 'userid', 'by', + 'byid', 'timestamp', 'expiry', 'reason', @@ -264,7 +260,7 @@ class ApiQueryBlocks extends ApiQueryBase { return array( 'start' => 'The timestamp to start enumerating from', 'end' => 'The timestamp to stop enumerating at', - 'dir' => 'The direction in which to enumerate', + 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ), 'ids' => 'Pipe-separated list of block IDs to list (optional)', 'users' => 'Pipe-separated list of users to search for (optional)', 'ip' => array( 'Get all blocks applying to this IP or CIDR range, including range blocks.', @@ -272,9 +268,11 @@ class ApiQueryBlocks extends ApiQueryBase { 'limit' => 'The maximum amount of blocks to list', 'prop' => array( 'Which properties to get', - ' id - Adds the id of the block', + ' id - Adds the ID of the block', ' user - Adds the username of the blocked user', - ' by - Adds the username of the blocking admin', + ' userid - Adds the user ID of the blocked user', + ' by - Adds the username of the blocking user', + ' byid - Adds the user ID of the blocking user', ' timestamp - Adds the timestamp of when the block was given', ' expiry - Adds the timestamp of when the block expires', ' reason - Adds the reason given for the block', @@ -290,7 +288,7 @@ class ApiQueryBlocks extends ApiQueryBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'usersandip', 'info' => 'bkusers and bkip cannot be used together' ), + $this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ), array( 'code' => 'cidrtoobroad', 'info' => 'CIDR ranges broader than /16 are not accepted' ), array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ), array( 'code' => 'param_user', 'info' => 'User name user is not valid' ), @@ -304,7 +302,11 @@ class ApiQueryBlocks extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Blocks'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryBlocks.php 73858 2010-09-28 01:21:15Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryBlocks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index b2769dc2..c2942493 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return + */ private function run( $resultPageSet = null ) { if ( $this->getPageSet()->getGoodTitleCount() == 0 ) { return; // nothing to do @@ -100,7 +104,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { } if ( isset( $show['hidden'] ) && isset( $show['!hidden'] ) ) { - $this->dieUsageMsg( array( 'show' ) ); + $this->dieUsageMsg( 'show' ); } if ( isset( $show['hidden'] ) || isset( $show['!hidden'] ) || isset( $prop['hidden'] ) ) { @@ -246,7 +250,11 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#categories_.2F_cl'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategories.php 86474 2011-04-20 13:22:05Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategories.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryCategoryInfo.php b/includes/api/ApiQueryCategoryInfo.php index d4b64025..dd3bc391 100644 --- a/includes/api/ApiQueryCategoryInfo.php +++ b/includes/api/ApiQueryCategoryInfo.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); - $categoryTitle = Title::newFromText( $params['title'] ); + $this->requireOnlyOneParameter( $params, 'title', 'pageid' ); + + if ( isset( $params['title'] ) ) { + $categoryTitle = Title::newFromText( $params['title'] ); - if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY ) { - $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' ); + if ( is_null( $categoryTitle ) || $categoryTitle->getNamespace() != NS_CATEGORY ) { + $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' ); + } + } elseif( isset( $params['pageid'] ) ) { + $categoryTitle = Title::newFromID( $params['pageid'] ); + + if ( !$categoryTitle ) { + $this->dieUsageMsg( array( 'nosuchpageid', $params['pageid'] ) ); + } elseif ( $categoryTitle->getNamespace() != NS_CATEGORY ) { + $this->dieUsage( 'The category name you entered is not valid', 'invalidcategory' ); + } } $prop = array_flip( $params['prop'] ); @@ -113,11 +129,11 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'by the previous query', '_badcontinue' ); } - + // Remove the types to skip from $queryTypes $contTypeIndex = array_search( $cont[0], $queryTypes ); $queryTypes = array_slice( $queryTypes, $contTypeIndex ); - + // Add a WHERE clause for sortkey and from // pack( "H*", $foo ) is used to convert hex back to binary $escSortkey = $this->getDB()->addQuotes( pack( "H*", $cont[1] ) ); @@ -127,13 +143,22 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $contWhere = "cl_sortkey $op $escSortkey OR " . "(cl_sortkey = $escSortkey AND " . "cl_from $op= $from)"; - + // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them + $this->addWhereRange( 'cl_sortkey', $dir, null, null ); + $this->addWhereRange( 'cl_from', $dir, null, null ); } else { + $startsortkey = $params['startsortkeyprefix'] !== null ? + Collation::singleton()->getSortkey( $params['startsortkeyprefix'] ) : + $params['startsortkey']; + $endsortkey = $params['endsortkeyprefix'] !== null ? + Collation::singleton()->getSortkey( $params['endsortkeyprefix'] ) : + $params['endsortkey']; + // The below produces ORDER BY cl_sortkey, cl_from, possibly with DESC added to each of them $this->addWhereRange( 'cl_sortkey', $dir, - $params['startsortkey'], - $params['endsortkey'] ); + $startsortkey, + $endsortkey ); $this->addWhereRange( 'cl_from', $dir, null, null ); } $this->addOption( 'USE INDEX', 'cl_sortkey' ); @@ -173,6 +198,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { $res = $this->select( __METHOD__ ); $rows = iterator_to_array( $res ); } + + $result = $this->getResult(); $count = 0; foreach ( $rows as $row ) { if ( ++ $count > $limit ) { @@ -218,7 +245,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { if ( $fld_timestamp ) { $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cl_timestamp ); } - $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); if ( !$fit ) { if ( $params['sort'] == 'timestamp' ) { @@ -237,7 +264,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { } if ( is_null( $resultPageSet ) ) { - $this->getResult()->setIndexedTagName_internal( + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'cm' ); } } @@ -246,7 +273,9 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { return array( 'title' => array( ApiBase::PARAM_TYPE => 'string', - ApiBase::PARAM_REQUIRED => true + ), + 'pageid' => array( + ApiBase::PARAM_TYPE => 'integer' ), 'prop' => array( ApiBase::PARAM_DFLT => 'ids|title', @@ -303,6 +332,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { ), 'startsortkey' => null, 'endsortkey' => null, + 'startsortkeyprefix' => null, + 'endsortkeyprefix' => null, ); } @@ -310,7 +341,8 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { global $wgMiserMode; $p = $this->getModulePrefix(); $desc = array( - 'title' => 'Which category to enumerate (required). Must include Category: prefix', + 'title' => "Which category to enumerate (required). Must include Category: prefix. Cannot be used together with {$p}pageid", + 'pageid' => "Page ID of the category to enumerate. Cannot be used together with {$p}title", 'prop' => array( 'What pieces of information to include', ' ids - Adds the page ID', @@ -326,17 +358,20 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'dir' => 'In which direction to sort', 'start' => "Timestamp to start listing from. Can only be used with {$p}sort=timestamp", 'end' => "Timestamp to end listing at. Can only be used with {$p}sort=timestamp", - 'startsortkey' => "Sortkey to start listing from. Can only be used with {$p}sort=sortkey", - 'endsortkey' => "Sortkey to end listing at. Can only be used with {$p}sort=sortkey", + 'startsortkey' => "Sortkey to start listing from. Must be given in binary format. Can only be used with {$p}sort=sortkey", + 'endsortkey' => "Sortkey to end listing at. Must be given in binary format. Can only be used with {$p}sort=sortkey", + 'startsortkeyprefix' => "Sortkey prefix to start listing from. Can only be used with {$p}sort=sortkey. Overrides {$p}startsortkey", + 'endsortkeyprefix' => "Sortkey prefix to end listing BEFORE (not at, if this value occurs it will not be included!). Can only be used with {$p}sort=sortkey. Overrides {$p}endsortkey", 'continue' => 'For large categories, give the value retured from previous query', 'limit' => 'The maximum number of pages to return.', ); + if ( $wgMiserMode ) { $desc['namespace'] = array( $desc['namespace'], - 'NOTE: Due to $wgMiserMode, using this may result in fewer than "limit" results', + "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results", 'returned before continuing; in extreme cases, zero results may be returned.', - 'Note that you can use cmtype=subcat or cmtype=file instead of cmnamespace=14 or 6.', + "Note that you can use {$p}type=subcat or {$p}type=file instead of {$p}namespace=14 or 6.", ); } return $desc; @@ -347,11 +382,14 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'notitle', 'info' => 'The cmtitle parameter is required' ), - array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ), - array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), - ) ); + return array_merge( parent::getPossibleErrors(), + $this->getRequireOnlyOneParameterErrorMessages( array( 'title', 'pageid' ) ), + array( + array( 'code' => 'invalidcategory', 'info' => 'The category name you entered is not valid' ), + array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), + array( 'nosuchpageid', 'pageid' ), + ) + ); } protected function getExamples() { @@ -363,7 +401,11 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Categorymembers'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 86474 2011-04-20 13:22:05Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryCategoryMembers.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 523862c0..f58b9ee5 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,6 +50,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $db = $this->getDB(); $params = $this->extractRequestParams( false ); $prop = array_flip( $params['prop'] ); + $fld_parentid = isset( $prop['parentid'] ); $fld_revid = isset( $prop['revid'] ); $fld_user = isset( $prop['user'] ); $fld_userid = isset( $prop['userid'] ); @@ -65,9 +66,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $titles = $pageSet->getTitles(); // This module operates in three modes: - // 'revs': List deleted revs for certain titles - // 'user': List deleted revs by a certain user - // 'all': List all deleted revs + // 'revs': List deleted revs for certain titles (1) + // 'user': List deleted revs by a certain user (2) + // 'all': List all deleted revs in NS (3) $mode = 'all'; if ( count( $titles ) > 0 ) { $mode = 'revs'; @@ -75,6 +76,21 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $mode = 'user'; } + if ( $mode == 'revs' || $mode == 'user' ) { + // Ignore namespace and unique due to inability to know whether they were purposely set + foreach( array( 'from', 'to', 'prefix', /*'namespace',*/ 'continue', /*'unique'*/ ) as $p ) { + if ( !is_null( $params[$p] ) ) { + $this->dieUsage( "The '{$p}' parameter cannot be used in modes 1 or 2", 'badparams'); + } + } + } else { + foreach( array( 'start', 'end' ) as $p ) { + if ( !is_null( $params[$p] ) ) { + $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams'); + } + } + } + if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) { $this->dieUsage( 'user and excludeuser cannot be used together', 'badparams' ); } @@ -82,24 +98,15 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $this->addTables( 'archive' ); $this->addWhere( 'ar_deleted = 0' ); $this->addFields( array( 'ar_title', 'ar_namespace', 'ar_timestamp' ) ); - if ( $fld_revid ) { - $this->addFields( 'ar_rev_id' ); - } - if ( $fld_user ) { - $this->addFields( 'ar_user_text' ); - } - if ( $fld_userid ) { - $this->addFields( 'ar_user' ); - } - if ( $fld_comment || $fld_parsedcomment ) { - $this->addFields( 'ar_comment' ); - } - if ( $fld_minor ) { - $this->addFields( 'ar_minor_edit' ); - } - if ( $fld_len ) { - $this->addFields( 'ar_len' ); - } + + $this->addFieldsIf( 'ar_parent_id', $fld_parentid ); + $this->addFieldsIf( 'ar_rev_id', $fld_revid ); + $this->addFieldsIf( 'ar_user_text', $fld_user ); + $this->addFieldsIf( 'ar_user', $fld_userid ); + $this->addFieldsIf( 'ar_comment', $fld_comment || $fld_parsedcomment ); + $this->addFieldsIf( 'ar_minor_edit', $fld_minor ); + $this->addFieldsIf( 'ar_len', $fld_len ); + if ( $fld_content ) { $this->addTables( 'text' ); $this->addFields( array( 'ar_text', 'ar_text_id', 'old_text', 'old_flags' ) ); @@ -125,9 +132,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase { if ( $fld_token ) { // Undelete tokens are identical for all pages, so we cache one here - $token = $wgUser->editToken(); + $token = $wgUser->editToken( '', $this->getMain()->getRequest() ); } + $dir = $params['dir']; + // We need a custom WHERE clause that matches all titles. if ( $mode == 'revs' ) { $lb = new LinkBatch( $titles ); @@ -135,9 +144,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $this->addWhere( $where ); } elseif ( $mode == 'all' ) { $this->addWhereFld( 'ar_namespace', $params['namespace'] ); - if ( !is_null( $params['from'] ) ) { - $from = $this->getDB()->strencode( $this->titleToKey( $params['from'] ) ); - $this->addWhere( "ar_title >= '$from'" ); + + $from = is_null( $params['from'] ) ? null : $this->titleToKey( $params['from'] ); + $to = is_null( $params['to'] ) ? null : $this->titleToKey( $params['to'] ); + $this->addWhereRange( 'ar_title', $dir, $from, $to ); + + if ( isset( $params['prefix'] ) ) { + $this->addWhere( 'ar_title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } } @@ -148,8 +161,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $this->getDB()->addQuotes( $params['excludeuser'] ) ); } - if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) - { + if ( !is_null( $params['continue'] ) && ( $mode == 'all' || $mode == 'revs' ) ) { $cont = explode( '|', $params['continue'] ); if ( count( $cont ) != 3 ) { $this->dieUsage( 'Invalid continue param. You should pass the original value returned by the previous query', 'badcontinue' ); @@ -157,7 +169,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $ns = intval( $cont[0] ); $title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) ); $ts = $this->getDB()->strencode( $cont[2] ); - $op = ( $params['dir'] == 'newer' ? '>' : '<' ); + $op = ( $dir == 'newer' ? '>' : '<' ); $this->addWhere( "ar_namespace $op $ns OR " . "(ar_namespace = $ns AND " . "(ar_title $op '$title' OR " . @@ -170,17 +182,16 @@ class ApiQueryDeletedrevs extends ApiQueryBase { if ( $mode == 'all' ) { if ( $params['unique'] ) { $this->addOption( 'GROUP BY', 'ar_title' ); - $this->addOption( 'ORDER BY', 'ar_title' ); } else { $this->addOption( 'ORDER BY', 'ar_title, ar_timestamp' ); } } else { if ( $mode == 'revs' ) { // Sort by ns and title in the same order as timestamp for efficiency - $this->addWhereRange( 'ar_namespace', $params['dir'], null, null ); - $this->addWhereRange( 'ar_title', $params['dir'], null, null ); + $this->addWhereRange( 'ar_namespace', $dir, null, null ); + $this->addWhereRange( 'ar_title', $dir, null, null ); } - $this->addWhereRange( 'ar_timestamp', $params['dir'], $params['start'], $params['end'] ); + $this->addTimestampWhereRange( 'ar_timestamp', $dir, $params['start'], $params['end'] ); } $res = $this->select( __METHOD__ ); $pageMap = array(); // Maps ns&title to (fake) pageid @@ -203,6 +214,9 @@ class ApiQueryDeletedrevs extends ApiQueryBase { if ( $fld_revid ) { $rev['revid'] = intval( $row->ar_rev_id ); } + if ( $fld_parentid ) { + $rev['parentid'] = intval( $row->ar_parent_id ); + } if ( $fld_user ) { $rev['user'] = $row->ar_user_text; } @@ -273,6 +287,8 @@ class ApiQueryDeletedrevs extends ApiQueryBase { ApiBase::PARAM_DFLT => 'older' ), 'from' => null, + 'to' => null, + 'prefix' => null, 'continue' => null, 'unique' => false, 'user' => array( @@ -296,6 +312,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { ApiBase::PARAM_DFLT => 'user|comment', ApiBase::PARAM_TYPE => array( 'revid', + 'parentid', 'user', 'userid', 'comment', @@ -314,13 +331,17 @@ class ApiQueryDeletedrevs extends ApiQueryBase { return array( 'start' => 'The timestamp to start enumerating from (1,2)', 'end' => 'The timestamp to stop enumerating at (1,2)', - 'dir' => 'The direction in which to enumerate (1,2)', + 'dir' => $this->getDirectionDescription( $this->getModulePrefix(), ' (1, 3)' ), + 'from' => 'Start listing at this title (3)', + 'to' => 'Stop listing at this title (3)', + 'prefix' => 'Search for all page titles that begin with this value (3)', 'limit' => 'The maximum amount of revisions to list', 'prop' => array( 'Which properties to get', - ' revid - Adds the revision id of the deleted revision', + ' revid - Adds the revision ID of the deleted revision', + ' parentid - Adds the revision ID of the previous revision to the page', ' user - Adds the user who made the revision', - ' userid - Adds the user id whom made the revision', + ' userid - Adds the user ID whom made the revision', ' comment - Adds the comment of the revision', ' parsedcomment - Adds the parsed comment of the revision', ' minor - Tags if the revision is minor', @@ -331,19 +352,19 @@ class ApiQueryDeletedrevs extends ApiQueryBase { 'namespace' => 'Only list pages in this namespace (3)', 'user' => 'Only list revisions by this user', 'excludeuser' => 'Don\'t list revisions by this user', - 'from' => 'Start listing at this title (3)', 'continue' => 'When more results are available, use this to continue (3)', 'unique' => 'List only one revision for each page (3)', ); } public function getDescription() { + $p = $this->getModulePrefix(); return array( 'List deleted revisions.', - 'This module operates in three modes:', - '1) List deleted revisions for the given title(s), sorted by timestamp', - '2) List deleted contributions for the given user, sorted by timestamp (no titles specified)', - '3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, druser not set)', + 'Operates in three modes:', + ' 1) List deleted revisions for the given title(s), sorted by timestamp', + ' 2) List deleted contributions for the given user, sorted by timestamp (no titles specified)', + " 3) List all deleted revisions in the given namespace, sorted by title and timestamp (no titles specified, {$p}user not set)", 'Certain parameters only apply to some modes and are ignored in others.', 'For instance, a parameter marked (1) only applies to mode 1 and is ignored in modes 2 and 3', ); @@ -355,6 +376,12 @@ class ApiQueryDeletedrevs extends ApiQueryBase { array( 'code' => 'badparams', 'info' => 'user and excludeuser cannot be used together' ), array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revision content' ), array( 'code' => 'badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), + array( 'code' => 'badparams', 'info' => "The 'from' parameter cannot be used in modes 1 or 2" ), + array( 'code' => 'badparams', 'info' => "The 'to' parameter cannot be used in modes 1 or 2" ), + array( 'code' => 'badparams', 'info' => "The 'prefix' parameter cannot be used in modes 1 or 2" ), + array( 'code' => 'badparams', 'info' => "The 'continue' parameter cannot be used in modes 1 or 2" ), + array( 'code' => 'badparams', 'info' => "The 'start' parameter cannot be used in mode 3" ), + array( 'code' => 'badparams', 'info' => "The 'end' parameter cannot be used in mode 3" ), ) ); } @@ -371,7 +398,11 @@ class ApiQueryDeletedrevs extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Deletedrevs'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiQueryDeletedrevs.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryDisabled.php b/includes/api/ApiQueryDisabled.php index b5712069..c9edd2e4 100644 --- a/includes/api/ApiQueryDisabled.php +++ b/includes/api/ApiQueryDisabled.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -68,6 +68,6 @@ class ApiQueryDisabled extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDisabled.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryDisabled.php 79969 2011-01-10 22:36:26Z reedy $'; } } diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php index ffe98038..4c7c1fc2 100644 --- a/includes/api/ApiQueryDuplicateFiles.php +++ b/includes/api/ApiQueryDuplicateFiles.php @@ -1,10 +1,10 @@ ,@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -52,6 +52,10 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return + */ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); $namespaces = $this->getPageSet()->getAllTitlesByNamespace(); @@ -164,7 +168,11 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#duplicatefiles_.2F_df'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryDuplicateFiles.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index ecd9e699..89928372 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); - $protocol = $params['protocol']; $query = $params['query']; + $protocol = self::getProtocolPrefix( $params['protocol'] ); - // Find the right prefix - global $wgUrlProtocols; - if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) ) { - foreach ( $wgUrlProtocols as $p ) { - if ( substr( $p, 0, strlen( $protocol ) ) === $protocol ) { - $protocol = $p; - break; - } - } - } else { - $protocol = null; - } - - $db = $this->getDB(); $this->addTables( array( 'page', 'externallinks' ) ); // must be in this order for 'USE INDEX' $this->addOption( 'USE INDEX', 'el_index' ); $this->addWhere( 'page_id=el_from' ); - $this->addWhereFld( 'page_namespace', $params['namespace'] ); - if ( !is_null( $query ) || $query != '' ) { - if ( is_null( $protocol ) ) { - $protocol = 'http://'; - } + global $wgMiserMode; + $miser_ns = array(); + if ( $wgMiserMode ) { + $miser_ns = $params['namespace']; + } else { + $this->addWhereFld( 'page_namespace', $params['namespace'] ); + } - $likeQuery = LinkFilter::makeLikeArray( $query, $protocol ); - if ( !$likeQuery ) { - $this->dieUsage( 'Invalid query', 'bad_query' ); - } + $whereQuery = $this->prepareUrlQuerySearchString( $query, $protocol ); - $likeQuery = LinkFilter::keepOneWildcard( $likeQuery ); - $this->addWhere( 'el_index ' . $db->buildLike( $likeQuery ) ); - } elseif ( !is_null( $protocol ) ) { - $this->addWhere( 'el_index ' . $db->buildLike( "$protocol", $db->anyString() ) ); + if ( $whereQuery !== null ) { + $this->addWhere( $whereQuery ); } $prop = array_flip( $params['prop'] ); @@ -125,6 +112,10 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { break; } + if ( count( $miser_ns ) && !in_array( $row->page_namespace, $miser_ns ) ) { + continue; + } + if ( is_null( $resultPageSet ) ) { $vals = array(); if ( $fld_ids ) { @@ -135,6 +126,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ApiQueryBase::addTitleInfo( $vals, $title ); } if ( $fld_url ) { + // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan $vals['url'] = $row->el_to; } $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); @@ -154,12 +146,6 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { } public function getAllowedParams() { - global $wgUrlProtocols; - $protocols = array( '' ); - foreach ( $wgUrlProtocols as $p ) { - $protocols[] = substr( $p, 0, strpos( $p, ':' ) ); - } - return array( 'prop' => array( ApiBase::PARAM_ISMULTI => true, @@ -174,7 +160,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ApiBase::PARAM_TYPE => 'integer' ), 'protocol' => array( - ApiBase::PARAM_TYPE => $protocols, + ApiBase::PARAM_TYPE => self::prepareProtocols(), ApiBase::PARAM_DFLT => '', ), 'query' => null, @@ -192,13 +178,42 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ); } + public static function prepareProtocols() { + global $wgUrlProtocols; + $protocols = array( '' ); + foreach ( $wgUrlProtocols as $p ) { + if ( $p !== '//' ) { + $protocols[] = substr( $p, 0, strpos( $p, ':' ) ); + } + } + return $protocols; + } + + public static function getProtocolPrefix( $protocol ) { + // Find the right prefix + global $wgUrlProtocols; + if ( $protocol && !in_array( $protocol, $wgUrlProtocols ) ) { + foreach ( $wgUrlProtocols as $p ) { + if ( substr( $p, 0, strlen( $protocol ) ) === $protocol ) { + $protocol = $p; + break; + } + } + + return $protocol; + } else { + return null; + } + } + public function getParamDescription() { + global $wgMiserMode; $p = $this->getModulePrefix(); - return array( + $desc = array( 'prop' => array( 'What pieces of information to include', - ' ids - Adds the id of page', - ' title - Adds the title and namespace id of the page', + ' ids - Adds the ID of page', + ' title - Adds the title and namespace ID of the page', ' url - Adds the URL used in the page', ), 'offset' => 'Used for paging. Use the value returned for "continue"', @@ -210,6 +225,16 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { 'namespace' => 'The page namespace(s) to enumerate.', 'limit' => 'How many pages to return.' ); + + if ( $wgMiserMode ) { + $desc['namespace'] = array( + $desc['namespace'], + "NOTE: Due to \$wgMiserMode, using this may result in fewer than \"{$p}limit\" results", + 'returned before continuing; in extreme cases, zero results may be returned', + ); + } + + return $desc; } public function getDescription() { @@ -228,7 +253,11 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Exturlusage'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryExtLinksUsage.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index fbfcbfb9..ca1efbb1 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -1,6 +1,6 @@ extractRequestParams(); + + $query = $params['query']; + $protocol = ApiQueryExtLinksUsage::getProtocolPrefix( $params['protocol'] ); + $this->addFields( array( 'el_from', 'el_to' @@ -54,13 +58,25 @@ class ApiQueryExternalLinks extends ApiQueryBase { $this->addTables( 'externallinks' ); $this->addWhereFld( 'el_from', array_keys( $this->getPageSet()->getGoodTitles() ) ); + $whereQuery = $this->prepareUrlQuerySearchString( $query, $protocol ); + + if ( $whereQuery !== null ) { + $this->addWhere( $whereQuery ); + } + // Don't order by el_from if it's constant in the WHERE clause if ( count( $this->getPageSet()->getGoodTitles() ) != 1 ) { $this->addOption( 'ORDER BY', 'el_from' ); } + // If we're querying all protocols, use DISTINCT to avoid repeating protocol-relative links twice + if ( $protocol === null ) { + $this->addOption( 'DISTINCT' ); + } + $this->addOption( 'LIMIT', $params['limit'] + 1 ); - if ( !is_null( $params['offset'] ) ) { + $offset = isset( $params['offset'] ) ? $params['offset'] : 0; + if ( $offset ) { $this->addOption( 'OFFSET', $params['offset'] ); } @@ -71,14 +87,15 @@ class ApiQueryExternalLinks extends ApiQueryBase { if ( ++$count > $params['limit'] ) { // We've reached the one extra which shows that // there are additional pages to be had. Stop here... - $this->setContinueEnumParameter( 'offset', @$params['offset'] + $params['limit'] ); + $this->setContinueEnumParameter( 'offset', $offset + $params['limit'] ); break; } $entry = array(); + // We *could* run this through wfExpandUrl() but I think it's better to output the link verbatim, even if it's protocol-relative --Roan ApiResult::setContent( $entry, $row->el_to ); $fit = $this->addPageSubItem( $row->el_from, $entry ); if ( !$fit ) { - $this->setContinueEnumParameter( 'offset', @$params['offset'] + $count - 1 ); + $this->setContinueEnumParameter( 'offset', $offset + $count - 1 ); break; } } @@ -97,14 +114,27 @@ class ApiQueryExternalLinks extends ApiQueryBase { ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), - 'offset' => null, + 'offset' => array( + ApiBase::PARAM_TYPE => 'integer' + ), + 'protocol' => array( + ApiBase::PARAM_TYPE => ApiQueryExtLinksUsage::prepareProtocols(), + ApiBase::PARAM_DFLT => '', + ), + 'query' => null, ); } public function getParamDescription() { + $p = $this->getModulePrefix(); return array( 'limit' => 'How many links to return', 'offset' => 'When more results are available, use this to continue', + 'protocol' => array( + "Protocol of the url. If empty and {$p}query set, the protocol is http.", + "Leave both this and {$p}query empty to list all external links" + ), + 'query' => 'Search string without protocol. Useful for checking whether a certain page contains a certain external url', ); } @@ -112,6 +142,12 @@ class ApiQueryExternalLinks extends ApiQueryBase { return 'Returns all external urls (not interwikies) from the given page(s)'; } + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'code' => 'bad_query', 'info' => 'Invalid query' ), + ) ); + } + protected function getExamples() { return array( 'Get a list of external links on the [[Main Page]]:', @@ -119,7 +155,11 @@ class ApiQueryExternalLinks extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#extlinks_.2F_el'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryExternalLinks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php index 05ccb346..541b25fc 100644 --- a/includes/api/ApiQueryFilearchive.php +++ b/includes/api/ApiQueryFilearchive.php @@ -59,7 +59,7 @@ class ApiQueryFilearchive extends ApiQueryBase { $fld_user = isset( $prop['user'] ); $fld_size = isset( $prop['size'] ); $fld_dimensions = isset( $prop['dimensions'] ); - $fld_description = isset( $prop['description'] ); + $fld_description = isset( $prop['description'] ) || isset( $prop['parseddescription'] ); $fld_mime = isset( $prop['mime'] ); $fld_metadata = isset( $prop['metadata'] ); $fld_bitdepth = isset( $prop['bitdepth'] ); @@ -69,45 +69,57 @@ class ApiQueryFilearchive extends ApiQueryBase { $this->addFields( array( 'fa_name', 'fa_deleted' ) ); $this->addFieldsIf( 'fa_storage_key', $fld_sha1 ); $this->addFieldsIf( 'fa_timestamp', $fld_timestamp ); - - if ( $fld_user ) { - $this->addFields( array( 'fa_user', 'fa_user_text' ) ); - } - $this->addFieldsIf( 'fa_size', $fld_size ); - - if ( $fld_dimensions ) { - $this->addFields( array( 'fa_height', 'fa_width' ) ); - } - + $this->addFieldsIf( array( 'fa_user', 'fa_user_text' ), $fld_user ); + $this->addFieldsIf( array( 'fa_height', 'fa_width', 'fa_size' ), $fld_dimensions || $fld_size ); $this->addFieldsIf( 'fa_description', $fld_description ); - - if ( $fld_mime ) { - $this->addFields( array( 'fa_major_mime', 'fa_minor_mime' ) ); - } - + $this->addFieldsIf( array( 'fa_major_mime', 'fa_minor_mime' ), $fld_mime ); $this->addFieldsIf( 'fa_metadata', $fld_metadata ); $this->addFieldsIf( 'fa_bits', $fld_bitdepth ); // Image filters $dir = ( $params['dir'] == 'descending' ? 'older' : 'newer' ); $from = ( is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); - $this->addWhereRange( 'fa_name', $dir, $from, null ); + $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); + $this->addWhereRange( 'fa_name', $dir, $from, $to ); if ( isset( $params['prefix'] ) ) { $this->addWhere( 'fa_name' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } - + + $sha1Set = isset( $params['sha1'] ); + $sha1base36Set = isset( $params['sha1base36'] ); + if ( $sha1Set || $sha1base36Set ) { + global $wgMiserMode; + if ( $wgMiserMode ) { + $this->dieUsage( 'Search by hash disabled in Miser Mode', 'hashsearchdisabled' ); + } + + $sha1 = false; + if ( $sha1Set ) { + if ( !$this->validateSha1Hash( $params['sha1'] ) ) { + $this->dieUsage( 'The SHA1 hash provided is not valid', 'invalidsha1hash' ); + } + $sha1 = wfBaseConvert( $params['sha1'], 16, 36, 31 ); + } elseif ( $sha1base36Set ) { + if ( !$this->validateSha1Base36Hash( $params['sha1base36'] ) ) { + $this->dieUsage( 'The SHA1Base36 hash provided is not valid', 'invalidsha1base36hash' ); + } + $sha1 = $params['sha1base36']; + } + if ( $sha1 ) { + $this->addWhere( 'fa_storage_key ' . $db->buildLike( "{$sha1}.", $db->anyString() ) ); + } + } + if ( !$wgUser->isAllowed( 'suppressrevision' ) ) { // Filter out revisions that the user is not allowed to see. There // is no way to indicate that we have skipped stuff because the // continuation parameter is fa_name - + // Note that this field is unindexed. This should however not be // a big problem as files with fa_deleted are rare $this->addWhereFld( 'fa_deleted', 0 ); } - - $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); $this->addOption( 'ORDER BY', 'fa_name' . @@ -127,9 +139,11 @@ class ApiQueryFilearchive extends ApiQueryBase { $file = array(); $file['name'] = $row->fa_name; + $title = Title::makeTitle( NS_FILE, $row->fa_name ); + self::addTitleInfo( $file, $title ); if ( $fld_sha1 ) { - $file['sha1'] = wfBaseConvert( $row->fa_storage_key, 36, 16, 40 ); + $file['sha1'] = wfBaseConvert( LocalRepo::getHashFromKey( $row->fa_storage_key ), 36, 16, 40 ); } if ( $fld_timestamp ) { $file['timestamp'] = wfTimestamp( TS_ISO_8601, $row->fa_timestamp ); @@ -138,18 +152,28 @@ class ApiQueryFilearchive extends ApiQueryBase { $file['userid'] = $row->fa_user; $file['user'] = $row->fa_user_text; } - if ( $fld_size ) { + if ( $fld_size || $fld_dimensions ) { $file['size'] = $row->fa_size; - } - if ( $fld_dimensions ) { + + $pageCount = ArchivedFile::newFromRow( $row )->pageCount(); + if ( $pageCount !== false ) { + $vals['pagecount'] = $pageCount; + } + $file['height'] = $row->fa_height; $file['width'] = $row->fa_width; } if ( $fld_description ) { $file['description'] = $row->fa_description; + if ( isset( $prop['parseddescription'] ) ) { + $file['parseddescription'] = $wgUser->getSkin()->formatComment( + $row->fa_description, $title ); + } } if ( $fld_metadata ) { - $file['metadata'] = $row->fa_metadata ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result ) : null; + $file['metadata'] = $row->fa_metadata + ? ApiQueryImageInfo::processMetaData( unserialize( $row->fa_metadata ), $result ) + : null; } if ( $fld_bitdepth ) { $file['bitdepth'] = $row->fa_bits; @@ -157,7 +181,7 @@ class ApiQueryFilearchive extends ApiQueryBase { if ( $fld_mime ) { $file['mime'] = "$row->fa_major_mime/$row->fa_minor_mime"; } - + if ( $row->fa_deleted & File::DELETED_FILE ) { $file['filehidden'] = ''; } @@ -172,7 +196,7 @@ class ApiQueryFilearchive extends ApiQueryBase { $file['suppressed'] = ''; } - + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $file ); if ( !$fit ) { $this->setContinueEnumParameter( 'from', $this->keyToTitle( $row->fa_name ) ); @@ -186,6 +210,7 @@ class ApiQueryFilearchive extends ApiQueryBase { public function getAllowedParams() { return array ( 'from' => null, + 'to' => null, 'prefix' => null, 'limit' => array( ApiBase::PARAM_DFLT => 10, @@ -201,6 +226,8 @@ class ApiQueryFilearchive extends ApiQueryBase { 'descending' ) ), + 'sha1' => null, + 'sha1base36' => null, 'prop' => array( ApiBase::PARAM_DFLT => 'timestamp', ApiBase::PARAM_ISMULTI => true, @@ -211,6 +238,7 @@ class ApiQueryFilearchive extends ApiQueryBase { 'size', 'dimensions', 'description', + 'parseddescription', 'mime', 'metadata', 'bitdepth' @@ -222,20 +250,24 @@ class ApiQueryFilearchive extends ApiQueryBase { public function getParamDescription() { return array( 'from' => 'The image title to start enumerating from', + 'to' => 'The image title to stop enumerating at', 'prefix' => 'Search for all image titles that begin with this value', 'dir' => 'The direction in which to list', - 'limit' => 'How many total images to return', + 'limit' => 'How many images to return in total', + 'sha1' => "SHA1 hash of image. Overrides {$this->getModulePrefix()}sha1base36. Disabled in Miser Mode", + 'sha1base36' => 'SHA1 hash of image in base 36 (used in MediaWiki). Disabled in Miser Mode', 'prop' => array( 'What image information to get:', - ' sha1 - Adds sha1 hash for the image', - ' timestamp - Adds timestamp for the uploaded version', - ' user - Adds user who uploaded the image version', - ' size - Adds the size of the image in bytes', - ' dimensions - Adds the height and width of the image', - ' description - Adds description the image version', - ' mime - Adds MIME of the image', - ' metadata - Lists EXIF metadata for the version of the image', - ' bitdepth - Adds the bit depth of the version', + ' sha1 - Adds SHA-1 hash for the image', + ' timestamp - Adds timestamp for the uploaded version', + ' user - Adds user who uploaded the image version', + ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)', + ' dimensions - Alias for size', + ' description - Adds description the image version', + ' parseddescription - Parse the description on the version', + ' mime - Adds MIME of the image', + ' metadata - Lists EXIF metadata for the version of the image', + ' bitdepth - Adds the bit depth of the version', ), ); } @@ -247,6 +279,9 @@ class ApiQueryFilearchive extends ApiQueryBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted file information' ), + array( 'code' => 'hashsearchdisabled', 'info' => 'Search by hash disabled in Miser Mode' ), + array( 'code' => 'invalidsha1hash', 'info' => 'The SHA1 hash provided is not valid' ), + array( 'code' => 'invalidsha1base36hash', 'info' => 'The SHA1Base36 hash provided is not valid' ), ) ); } @@ -259,6 +294,6 @@ class ApiQueryFilearchive extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryFilearchive.php 85354 2011-04-04 18:25:31Z demon $'; + return __CLASS__ . ': $Id: ApiQueryFilearchive.php 91246 2011-07-01 02:25:19Z reedy $'; } } diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php index 6958a253..1a8e9720 100644 --- a/includes/api/ApiQueryIWBacklinks.php +++ b/includes/api/ApiQueryIWBacklinks.php @@ -48,6 +48,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ public function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); @@ -115,11 +119,10 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase { if ( !is_null( $resultPageSet ) ) { $pages[] = Title::newFromRow( $row ); } else { - $entry = array(); + $entry = array( 'pageid' => $row->page_id ); - $entry['pageid'] = intval( $row->page_id ); - $entry['ns'] = intval( $row->page_namespace ); - $entry['title'] = $row->page_title; + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + ApiQueryBase::addTitleInfo( $entry, $title ); if ( $row->page_is_redirect ) { $entry['redirect'] = ''; @@ -212,6 +215,6 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryIWBacklinks.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryIWBacklinks.php 84257 2011-03-18 19:15:33Z reedy $'; } } diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php index e980d6a5..3215a96e 100644 --- a/includes/api/ApiQueryIWLinks.php +++ b/includes/api/ApiQueryIWLinks.php @@ -47,6 +47,11 @@ class ApiQueryIWLinks extends ApiQueryBase { } $params = $this->extractRequestParams(); + + if ( isset( $params['title'] ) && !isset( $params['prefix'] ) ) { + $this->dieUsageMsg( array( 'missingparam', 'prefix' ) ); + } + $this->addFields( array( 'iwl_from', 'iwl_prefix', @@ -74,12 +79,23 @@ class ApiQueryIWLinks extends ApiQueryBase { ); } - // Don't order by iwl_from if it's constant in the WHERE clause - if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { - $this->addOption( 'ORDER BY', 'iwl_prefix' ); + if ( isset( $params['prefix'] ) ) { + $this->addWhereFld( 'iwl_prefix', $params['prefix'] ); + if ( isset( $params['title'] ) ) { + $this->addWhereFld( 'iwl_title', $params['title'] ); + $this->addOption( 'ORDER BY', 'iwl_from' ); + } else { + $this->addOption( 'ORDER BY', 'iwl_title, iwl_from' ); + } } else { - $this->addOption( 'ORDER BY', 'iwl_from, iwl_prefix' ); + // Don't order by iwl_from if it's constant in the WHERE clause + if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { + $this->addOption( 'ORDER BY', 'iwl_prefix' ); + } else { + $this->addOption( 'ORDER BY', 'iwl_from, iwl_prefix' ); + } } + $this->addOption( 'LIMIT', $params['limit'] + 1 ); $res = $this->select( __METHOD__ ); @@ -96,7 +112,7 @@ class ApiQueryIWLinks extends ApiQueryBase { if ( !is_null( $params['url'] ) ) { $title = Title::newFromText( "{$row->iwl_prefix}:{$row->iwl_title}" ); if ( $title ) { - $entry['url'] = $title->getFullURL(); + $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } } @@ -124,6 +140,8 @@ class ApiQueryIWLinks extends ApiQueryBase { ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), 'continue' => null, + 'prefix' => null, + 'title' => null, ); } @@ -132,6 +150,8 @@ class ApiQueryIWLinks extends ApiQueryBase { 'url' => 'Whether to get the full URL', 'limit' => 'How many interwiki links to return', 'continue' => 'When more results are available, use this to continue', + 'prefix' => 'Prefix for the interwiki', + 'title' => "Interwiki link to search for. Must be used with {$this->getModulePrefix()}prefix", ); } @@ -141,6 +161,7 @@ class ApiQueryIWLinks extends ApiQueryBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( + array( 'missingparam', 'prefix' ), array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), ) ); } @@ -153,6 +174,6 @@ class ApiQueryIWLinks extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryIWLinks.php 77080 2010-11-21 17:27:13Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryIWLinks.php 96475 2011-09-07 19:37:56Z catrope $'; } } diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 21696be2..5bacd636 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -1,6 +1,6 @@ mergeThumbParams( $img, $scale, $params['urlparam'] ); + // Get information about the current version first // Check that the current version is within the start-end boundaries $gotOne = false; if ( ( is_null( $start ) || $img->getTimestamp() <= $start ) && ( is_null( $params['end'] ) || $img->getTimestamp() >= $params['end'] ) - ) - { + ) { $gotOne = true; + $fit = $this->addPageSubItem( $pageId, - self::getInfo( $img, $prop, $result, $scale ) ); + self::getInfo( $img, $prop, $result, + $finalThumbParams, $params['metadataversion'] ) ); if ( !$fit ) { if ( count( $pageIds[NS_IMAGE] ) == 1 ) { // See the 'the user is screwed' comment above @@ -147,7 +151,8 @@ class ApiQueryImageInfo extends ApiQueryBase { break; } $fit = $this->addPageSubItem( $pageId, - self::getInfo( $oldie, $prop, $result ) ); + self::getInfo( $oldie, $prop, $result, + $finalThumbParams, $params['metadataversion'] ) ); if ( !$fit ) { if ( count( $pageIds[NS_IMAGE] ) == 1 ) { $this->setContinueEnumParameter( 'start', @@ -179,14 +184,16 @@ class ApiQueryImageInfo extends ApiQueryBase { } /** - * From parameters, construct a 'scale' array - * @param $params Array: + * From parameters, construct a 'scale' array + * @param $params Array: Parameters passed to api. * @return Array or Null: key-val array of 'width' and 'height', or null - */ + */ public function getScale( $params ) { $p = $this->getModulePrefix(); + + // Height and width. if ( $params['urlheight'] != -1 && $params['urlwidth'] == -1 ) { - $this->dieUsage( "${p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" ); + $this->dieUsage( "{$p}urlheight cannot be used without {$p}urlwidth", "{$p}urlwidth" ); } if ( $params['urlwidth'] != -1 ) { @@ -195,10 +202,63 @@ class ApiQueryImageInfo extends ApiQueryBase { $scale['height'] = $params['urlheight']; } else { $scale = null; + if ( $params['urlparam'] ) { + $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" ); + } + return $scale; } + return $scale; } + /** Validate and merge scale parameters with handler thumb parameters, give error if invalid. + * + * We do this later than getScale, since we need the image + * to know which handler, since handlers can make their own parameters. + * @param File $image Image that params are for. + * @param Array $thumbParams thumbnail parameters from getScale + * @param String $otherParams of otherParams (iiurlparam). + * @return Array of parameters for transform. + */ + protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) { + if ( !$otherParams ) { + return $thumbParams; + } + $p = $this->getModulePrefix(); + + $h = $image->getHandler(); + if ( !$h ) { + $this->setWarning( 'Could not create thumbnail because ' . + $image->getName() . ' does not have an associated image handler' ); + return $thumbParams; + } + + $paramList = $h->parseParamString( $otherParams ); + if ( !$paramList ) { + // Just set a warning (instead of dieUsage), as in many cases + // we could still render the image using width and height parameters, + // and this type of thing could happen between different versions of + // handlers. + $this->setWarning( "Could not parse {$p}urlparam for " . $image->getName() + . '. Using only width and height' ); + return $thumbParams; + } + + if ( isset( $paramList['width'] ) ) { + if ( intval( $paramList['width'] ) != intval( $thumbParams['width'] ) ) { + $this->dieUsage( "{$p}urlparam had width of {$paramList['width']} but " + . "{$p}urlwidth was {$thumbParams['width']}", "urlparam_urlwidth_mismatch" ); + } + } + + foreach ( $paramList as $name => $value ) { + if ( !$h->validateParam( $name, $value ) ) { + $this->dieUsage( "Invalid value for {$p}urlparam ($name=$value)", "urlparam" ); + } + } + + return $thumbParams + $paramList; + } /** * Get result information for an image revision @@ -206,10 +266,11 @@ class ApiQueryImageInfo extends ApiQueryBase { * @param $file File object * @param $prop Array of properties to get (in the keys) * @param $result ApiResult object - * @param $scale Array containing 'width' and 'height' items, or null + * @param $thumbParams Array containing 'width' and 'height' items, or null + * @param $version string Version of image metadata (for things like jpeg which have different versions). * @return Array: result array */ - static function getInfo( $file, $prop, $result, $scale = null ) { + static function getInfo( $file, $prop, $result, $thumbParams = null, $version = 'latest' ) { $vals = array(); // Timestamp is shown even if the file is revdelete'd in interface // so do same here. @@ -242,7 +303,7 @@ class ApiQueryImageInfo extends ApiQueryBase { $vals['size'] = intval( $file->getSize() ); $vals['width'] = intval( $file->getWidth() ); $vals['height'] = intval( $file->getHeight() ); - + $pageCount = $file->pageCount(); if ( $pageCount !== false ) { $vals['pagecount'] = $pageCount; @@ -271,10 +332,11 @@ class ApiQueryImageInfo extends ApiQueryBase { $sha1 = isset( $prop['sha1'] ); $meta = isset( $prop['metadata'] ); $mime = isset( $prop['mime'] ); + $mediatype = isset( $prop['mediatype'] ); $archive = isset( $prop['archivename'] ); $bitdepth = isset( $prop['bitdepth'] ); - if ( ( $url || $sha1 || $meta || $mime || $archive || $bitdepth ) + if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth ) && $file->isDeleted( File::DELETED_FILE ) ) { $vals['filehidden'] = ''; @@ -283,10 +345,10 @@ class ApiQueryImageInfo extends ApiQueryBase { } if ( $url ) { - if ( !is_null( $scale ) && !$file->isOld() ) { - $mto = $file->transform( array( 'width' => $scale['width'], 'height' => $scale['height'] ) ); + if ( !is_null( $thumbParams ) ) { + $mto = $file->transform( $thumbParams ); if ( $mto && !$mto->isError() ) { - $vals['thumburl'] = wfExpandUrl( $mto->getUrl() ); + $vals['thumburl'] = wfExpandUrl( $mto->getUrl(), PROTO_CURRENT ); // bug 23834 - If the URL's are the same, we haven't resized it, so shouldn't give the wanted // thumbnail sizes for the thumbnail actual size @@ -299,32 +361,39 @@ class ApiQueryImageInfo extends ApiQueryBase { } if ( isset( $prop['thumbmime'] ) && $file->getHandler() ) { - list( $ext, $mime ) = $file->getHandler()->getThumbType( - substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ), + list( $ext, $mime ) = $file->getHandler()->getThumbType( + substr( $mto->getPath(), strrpos( $mto->getPath(), '.' ) + 1 ), $file->getMimeType(), $thumbParams ); $vals['thumbmime'] = $mime; } - } else if ( $mto && $mto->isError() ) { + } elseif ( $mto && $mto->isError() ) { $vals['thumberror'] = $mto->toText(); } } - $vals['url'] = $file->getFullURL(); - $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl() ); + $vals['url'] = wfExpandUrl( $file->getFullURL(), PROTO_CURRENT ); + $vals['descriptionurl'] = wfExpandUrl( $file->getDescriptionUrl(), PROTO_CURRENT ); } - + if ( $sha1 ) { $vals['sha1'] = wfBaseConvert( $file->getSha1(), 36, 16, 40 ); } if ( $meta ) { - $metadata = $file->getMetadata(); - $vals['metadata'] = $metadata ? self::processMetaData( unserialize( $metadata ), $result ) : null; + $metadata = unserialize( $file->getMetadata() ); + if ( $version !== 'latest' ) { + $metadata = $file->convertMetadataVersion( $metadata, $version ); + } + $vals['metadata'] = $metadata ? self::processMetaData( $metadata, $result ) : null; } if ( $mime ) { $vals['mime'] = $file->getMimeType(); } + if ( $mediatype ) { + $vals['mediatype'] = $file->getMediaType(); + } + if ( $archive && $file->isOld() ) { $vals['archivename'] = $file->getArchiveName(); } @@ -336,7 +405,7 @@ class ApiQueryImageInfo extends ApiQueryBase { return $vals; } - /* + /** * * @param $metadata Array * @param $result ApiResult @@ -363,6 +432,10 @@ class ApiQueryImageInfo extends ApiQueryBase { return 'public'; } + /** + * @param $img File + * @return string + */ private function getContinueStr( $img ) { return $img->getOriginalTitle()->getText() . '|' . $img->getTimestamp(); @@ -396,63 +469,86 @@ class ApiQueryImageInfo extends ApiQueryBase { ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_DFLT => -1 ), + 'metadataversion' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_DFLT => '1', + ), + 'urlparam' => array( + ApiBase::PARAM_DFLT => '', + ApiBase::PARAM_TYPE => 'string', + ), 'continue' => null, ); } /** * Returns all possible parameters to iiprop + * + * @param array $filter List of properties to filter out + * + * @return Array + */ + public static function getPropertyNames( $filter = array() ) { + return array_diff( array_keys( self::getProperties() ), $filter ); + } + + /** + * Returns array key value pairs of properties and their descriptions + * + * @return array */ - public static function getPropertyNames() { + private static function getProperties() { return array( - 'timestamp', - 'user', - 'userid', - 'comment', - 'parsedcomment', - 'url', - 'size', - 'dimensions', // For backwards compatibility with Allimages - 'sha1', - 'mime', - 'thumbmime', - 'metadata', - 'archivename', - 'bitdepth', + 'timestamp' => ' timestamp - Adds timestamp for the uploaded version', + 'user' => ' user - Adds the user who uploaded the image version', + 'userid' => ' userid - Add the user ID that uploaded the image version', + 'comment' => ' comment - Comment on the version', + 'parsedcomment' => ' parsedcomment - Parse the comment on the version', + 'url' => ' url - Gives URL to the image and the description page', + 'size' => ' size - Adds the size of the image in bytes and the height, width and page count (if applicable)', + 'dimensions' => ' dimensions - Alias for size', // For backwards compatibility with Allimages + 'sha1' => ' sha1 - Adds SHA-1 hash for the image', + 'mime' => ' mime - Adds MIME type of the image', + 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail (requires url)', + 'mediatype' => ' mediatype - Adds the media type of the image', + 'metadata' => ' metadata - Lists EXIF metadata for the version of the image', + 'archivename' => ' archivename - Adds the file name of the archive version for non-latest versions', + 'bitdepth' => ' bitdepth - Adds the bit depth of the version', ); } + /** + * Returns the descriptions for the properties provided by getPropertyNames() + * + * @param array $filter List of properties to filter out + * + * @return array + */ + public static function getPropertyDescriptions( $filter = array() ) { + return array_merge( + array( 'What image information to get:' ), + array_values( array_diff_key( self::getProperties(), array_flip( $filter ) ) ) + ); + } /** - * Return the API documentation for the parameters. - * @return {Array} parameter documentation. + * Return the API documentation for the parameters. + * @return Array parameter documentation. */ public function getParamDescription() { $p = $this->getModulePrefix(); return array( - 'prop' => array( - 'What image information to get:', - ' timestamp - Adds timestamp for the uploaded version', - ' user - Adds the user who uploaded the image version', - ' userid - Add the user id that uploaded the image version', - ' comment - Comment on the version', - ' parsedcomment - Parse the comment on the version', - ' url - Gives URL to the image and the description page', - ' size - Adds the size of the image in bytes and the height and width', - ' dimensions - Alias for size', - ' sha1 - Adds sha1 hash for the image', - ' mime - Adds MIME of the image', - ' thumbmime - Adss MIME of the image thumbnail (requires url)', - ' metadata - Lists EXIF metadata for the version of the image', - ' archivename - Adds the file name of the archive version for non-latest versions', - ' bitdepth - Adds the bit depth of the version', - ), + 'prop' => self::getPropertyDescriptions(), 'urlwidth' => array( "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.", 'Only the current version of the image can be scaled' ), 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth", + 'urlparam' => array( "A handler specific parameter string. For example, pdf's ", + "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ), 'limit' => 'How many image revisions to return', 'start' => 'Timestamp to start listing from', 'end' => 'Timestamp to stop listing at', + 'metadataversion' => array( "Version of metadata to use. if 'latest' is specified, use latest version.", + "Defaults to '1' for backwards compatibility" ), 'continue' => 'If the query response includes a continue value, use it here to get another page of results' ); } @@ -462,8 +558,13 @@ class ApiQueryImageInfo extends ApiQueryBase { } public function getPossibleErrors() { + $p = $this->getModulePrefix(); return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'iiurlwidth', 'info' => 'iiurlheight cannot be used without iiurlwidth' ), + array( 'code' => "{$p}urlwidth", 'info' => "{$p}urlheight cannot be used without {$p}urlwidth" ), + array( 'code' => 'urlparam', 'info' => "Invalid value for {$p}urlparam" ), + array( 'code' => 'urlparam_no_width', 'info' => "{$p}urlparam requires {$p}urlwidth" ), + array( 'code' => 'urlparam_urlwidth_mismatch', 'info' => "The width set in {$p}urlparm doesnt't " . + "match the one in {$p}urlwidth" ), ) ); } @@ -474,7 +575,11 @@ class ApiQueryImageInfo extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#imageinfo_.2F_ii'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImageInfo.php 85435 2011-04-05 14:00:08Z demon $'; + return __CLASS__ . ': $Id: ApiQueryImageInfo.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryImages.php b/includes/api/ApiQueryImages.php index af2920c7..9dfdf341 100644 --- a/includes/api/ApiQueryImages.php +++ b/includes/api/ApiQueryImages.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + */ private function run( $resultPageSet = null ) { if ( $this->getPageSet()->getGoodTitleCount() == 0 ) { return; // nothing to do @@ -84,6 +87,19 @@ class ApiQueryImages extends ApiQueryGeneratorBase { } $this->addOption( 'LIMIT', $params['limit'] + 1 ); + if ( !is_null( $params['images'] ) ) { + $images = array(); + foreach ( $params['images'] as $img ) { + $title = Title::newFromText( $img ); + if ( !$title || $title->getNamespace() != NS_FILE ) { + $this->setWarning( "``$img'' is not a file" ); + } else { + $images[] = $title->getDBkey(); + } + } + $this->addWhereFld( 'il_to', $images ); + } + $res = $this->select( __METHOD__ ); if ( is_null( $resultPageSet ) ) { @@ -136,6 +152,9 @@ class ApiQueryImages extends ApiQueryGeneratorBase { ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 ), 'continue' => null, + 'images' => array( + ApiBase::PARAM_ISMULTI => true, + ) ); } @@ -143,6 +162,7 @@ class ApiQueryImages extends ApiQueryGeneratorBase { return array( 'limit' => 'How many images to return', 'continue' => 'When more results are available, use this to continue', + 'images' => 'Only list these images. Useful for checking whether a certain page has a certain Image.', ); } @@ -165,7 +185,11 @@ class ApiQueryImages extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#images_.2F_im'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryImages.php 73543 2010-09-22 16:50:09Z platonides $'; + return __CLASS__ . ': $Id: ApiQueryImages.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 59f61de1..fef1c6fc 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -1,6 +1,6 @@ array( 'ApiQueryInfo', 'getUnblockToken' ), 'email' => array( 'ApiQueryInfo', 'getEmailToken' ), 'import' => array( 'ApiQueryInfo', 'getImportToken' ), + 'watch' => array( 'ApiQueryInfo', 'getWatchToken'), ); wfRunHooks( 'APIQueryInfoTokens', array( &$this->tokenFunctions ) ); return $this->tokenFunctions; @@ -193,7 +205,7 @@ class ApiQueryInfo extends ApiQueryBase { public static function getImportToken( $pageid, $title ) { global $wgUser; - if ( !$wgUser->isAllowed( 'import' ) ) { + if ( !$wgUser->isAllowedAny( 'import', 'importupload' ) ) { return false; } @@ -206,6 +218,21 @@ class ApiQueryInfo extends ApiQueryBase { return $cachedImportToken; } + public static function getWatchToken( $pageid, $title ) { + global $wgUser; + if ( !$wgUser->isLoggedIn() ) { + return false; + } + + static $cachedWatchToken = null; + if ( !is_null( $cachedWatchToken ) ) { + return $cachedWatchToken; + } + + $cachedWatchToken = $wgUser->editToken( 'watch' ); + return $cachedWatchToken; + } + public function execute() { $this->params = $this->extractRequestParams(); if ( !is_null( $this->params['prop'] ) ) { @@ -353,8 +380,8 @@ class ApiQueryInfo extends ApiQueryBase { } if ( $this->fld_url ) { - $pageInfo['fullurl'] = $title->getFullURL(); - $pageInfo['editurl'] = $title->getFullURL( 'action=edit' ); + $pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); + $pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT ); } if ( $this->fld_readable && $title->userCanRead() ) { $pageInfo['readable'] = ''; @@ -386,6 +413,7 @@ class ApiQueryInfo extends ApiQueryBase { * Get information about protections and put it in $protections */ private function getProtectionInfo() { + global $wgContLang; $this->protections = array(); $db = $this->getDB(); @@ -404,7 +432,7 @@ class ApiQueryInfo extends ApiQueryBase { $a = array( 'type' => $row->pr_type, 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ) + 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ) ); if ( $row->pr_cascade ) { $a['cascade'] = ''; @@ -461,7 +489,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->protections[$row->pt_namespace][$row->pt_title][] = array( 'type' => 'create', 'level' => $row->pt_create_perm, - 'expiry' => Block::decodeExpiry( $row->pt_expiry, TS_ISO_8601 ) + 'expiry' => $wgContLang->formatExpiry( $row->pt_expiry, TS_ISO_8601 ) ); } } @@ -495,7 +523,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->protections[$row->tl_namespace][$row->tl_title][] = array( 'type' => $row->pr_type, 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), + 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); } @@ -518,7 +546,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->protections[NS_FILE][$row->il_to][] = array( 'type' => $row->pr_type, 'level' => $row->pr_level, - 'expiry' => Block::decodeExpiry( $row->pr_expiry, TS_ISO_8601 ), + 'expiry' => $wgContLang->formatExpiry( $row->pr_expiry, TS_ISO_8601 ), 'source' => $source->getPrefixedText() ); } @@ -700,7 +728,11 @@ class ApiQueryInfo extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#info_.2F_in'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryInfo.php 78439 2010-12-15 14:23:46Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryInfo.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php new file mode 100644 index 00000000..e09384e5 --- /dev/null +++ b/includes/api/ApiQueryLangBacklinks.php @@ -0,0 +1,220 @@ +@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +if ( !defined( 'MEDIAWIKI' ) ) { + // Eclipse helper - will be ignored in production + require_once( "ApiQueryBase.php" ); +} + +/** + * This gives links pointing to the given interwiki + * @ingroup API + */ +class ApiQueryLangBacklinks extends ApiQueryGeneratorBase { + + public function __construct( $query, $moduleName ) { + parent::__construct( $query, $moduleName, 'lbl' ); + } + + public function execute() { + $this->run(); + } + + public function executeGenerator( $resultPageSet ) { + $this->run( $resultPageSet ); + } + + /** + * @param $resultPageSet ApiPageSet + * @return void + */ + public function run( $resultPageSet = null ) { + $params = $this->extractRequestParams(); + + if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) { + $this->dieUsageMsg( array( 'missingparam', 'lang' ) ); + } + + if ( !is_null( $params['continue'] ) ) { + $cont = explode( '|', $params['continue'] ); + if ( count( $cont ) != 3 ) { + $this->dieUsage( 'Invalid continue param. You should pass the ' . + 'original value returned by the previous query', '_badcontinue' ); + } + + $prefix = $this->getDB()->strencode( $cont[0] ); + $title = $this->getDB()->strencode( $this->titleToKey( $cont[1] ) ); + $from = intval( $cont[2] ); + $this->addWhere( + "ll_lang > '$prefix' OR " . + "(ll_lang = '$prefix' AND " . + "(ll_title > '$title' OR " . + "(ll_title = '$title' AND " . + "ll_from >= $from)))" + ); + } + + $prop = array_flip( $params['prop'] ); + $lllang = isset( $prop['lllang'] ); + $lltitle = isset( $prop['lltitle'] ); + + $this->addTables( array( 'langlinks', 'page' ) ); + $this->addWhere( 'll_from = page_id' ); + + $this->addFields( array( 'page_id', 'page_title', 'page_namespace', 'page_is_redirect', + 'll_from', 'll_lang', 'll_title' ) ); + + if ( isset( $params['lang'] ) ) { + $this->addWhereFld( 'll_lang', $params['lang'] ); + if ( isset( $params['title'] ) ) { + $this->addWhereFld( 'll_title', $params['title'] ); + $this->addOption( 'ORDER BY', 'll_from' ); + } else { + $this->addOption( 'ORDER BY', 'll_title, ll_from' ); + } + } else { + $this->addOption( 'ORDER BY', 'll_lang, ll_title, ll_from' ); + } + + $this->addOption( 'LIMIT', $params['limit'] + 1 ); + + $res = $this->select( __METHOD__ ); + + $pages = array(); + + $count = 0; + $result = $this->getResult(); + foreach ( $res as $row ) { + if ( ++ $count > $params['limit'] ) { + // We've reached the one extra which shows that there are additional pages to be had. Stop here... + // Continue string preserved in case the redirect query doesn't pass the limit + $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" ); + break; + } + + if ( !is_null( $resultPageSet ) ) { + $pages[] = Title::newFromRow( $row ); + } else { + $entry = array( 'pageid' => $row->page_id ); + + $title = Title::makeTitle( $row->page_namespace, $row->page_title ); + ApiQueryBase::addTitleInfo( $entry, $title ); + + if ( $row->page_is_redirect ) { + $entry['redirect'] = ''; + } + + if ( $lllang ) { + $entry['lllang'] = $row->ll_lang; + } + + if ( $lltitle ) { + $entry['lltitle'] = $row->ll_title; + } + + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $entry ); + if ( !$fit ) { + $this->setContinueEnumParameter( 'continue', "{$row->ll_lang}|{$row->ll_title}|{$row->ll_from}" ); + break; + } + } + } + + if ( is_null( $resultPageSet ) ) { + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'll' ); + } else { + $resultPageSet->populateFromTitles( $pages ); + } + } + + public function getCacheMode( $params ) { + return 'public'; + } + + public function getAllowedParams() { + return array( + 'lang' => null, + 'title' => null, + 'continue' => null, + 'limit' => array( + ApiBase::PARAM_DFLT => 10, + ApiBase::PARAM_TYPE => 'limit', + ApiBase::PARAM_MIN => 1, + ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, + ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 + ), + 'prop' => array( + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_DFLT => '', + ApiBase::PARAM_TYPE => array( + 'lllang', + 'lltitle', + ), + ), + ); + } + + public function getParamDescription() { + return array( + 'lang' => 'Language for the language link', + 'title' => "Language link to search for. Must be used with {$this->getModulePrefix()}lang", + 'continue' => 'When more results are available, use this to continue', + 'prop' => array( + 'Which properties to get', + ' lllang - Adds the language code of the language link', + ' lltitle - Adds the title of the language ink', + ), + 'limit' => 'How many total pages to return', + ); + } + + public function getDescription() { + return array( 'Find all pages that link to the given language link.', + 'Can be used to find all links with a language code, or', + 'all links to a title (with a given language).', + 'Using neither parameter is effectively "All Language Links"', + ); + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + array( 'missingparam', 'lang' ), + array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), + ) ); + } + + protected function getExamples() { + return array( + 'api.php?action=query&list=langbacklinks&lbltitle=Test&lbllang=fr', + 'api.php?action=query&generator=langbacklinks&glbltitle=Test&lbllang=fr&prop=info' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryLangBacklinks.php 88429 2011-05-19 21:13:03Z reedy $'; + } +} diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index c2ecbfee..b2a974ad 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -1,6 +1,6 @@ extractRequestParams(); + + if ( isset( $params['title'] ) && !isset( $params['lang'] ) ) { + $this->dieUsageMsg( array( 'missingparam', 'lang' ) ); + } + $this->addFields( array( 'll_from', 'll_lang', @@ -69,12 +74,23 @@ class ApiQueryLangLinks extends ApiQueryBase { ); } - // Don't order by ll_from if it's constant in the WHERE clause - if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { - $this->addOption( 'ORDER BY', 'll_lang' ); + if ( isset( $params['lang'] ) ) { + $this->addWhereFld( 'll_lang', $params['lang'] ); + if ( isset( $params['title'] ) ) { + $this->addWhereFld( 'll_title', $params['title'] ); + $this->addOption( 'ORDER BY', 'll_from' ); + } else { + $this->addOption( 'ORDER BY', 'll_title, ll_from' ); + } } else { - $this->addOption( 'ORDER BY', 'll_from, ll_lang' ); + // Don't order by ll_from if it's constant in the WHERE clause + if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { + $this->addOption( 'ORDER BY', 'll_lang' ); + } else { + $this->addOption( 'ORDER BY', 'll_from, ll_lang' ); + } } + $this->addOption( 'LIMIT', $params['limit'] + 1 ); $res = $this->select( __METHOD__ ); @@ -90,7 +106,7 @@ class ApiQueryLangLinks extends ApiQueryBase { if ( $params['url'] ) { $title = Title::newFromText( "{$row->ll_lang}:{$row->ll_title}" ); if ( $title ) { - $entry['url'] = $title->getFullURL(); + $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } } ApiResult::setContent( $entry, $row->ll_title ); @@ -117,6 +133,8 @@ class ApiQueryLangLinks extends ApiQueryBase { ), 'continue' => null, 'url' => false, + 'lang' => null, + 'title' => null, ); } @@ -125,6 +143,8 @@ class ApiQueryLangLinks extends ApiQueryBase { 'limit' => 'How many langlinks to return', 'continue' => 'When more results are available, use this to continue', 'url' => 'Whether to get the full URL', + 'lang' => 'Language code', + 'title' => "Link to search for. Must be used with {$this->getModulePrefix()}lang", ); } @@ -134,6 +154,7 @@ class ApiQueryLangLinks extends ApiQueryBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( + array( 'missingparam', 'lang' ), array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), ) ); } @@ -145,7 +166,11 @@ class ApiQueryLangLinks extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#langlinks_.2F_ll'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLangLinks.php 77660 2010-12-03 14:44:07Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryLangLinks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryLinks.php b/includes/api/ApiQueryLinks.php index 4f3bad3b..fa2495a9 100644 --- a/includes/api/ApiQueryLinks.php +++ b/includes/api/ApiQueryLinks.php @@ -1,6 +1,6 @@ prefix = 'pl'; $this->description = 'link'; $this->titlesParam = 'titles'; + $this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#links_.2F_pl'; break; case self::TEMPLATES: $this->table = 'templatelinks'; $this->prefix = 'tl'; $this->description = 'template'; $this->titlesParam = 'templates'; + $this->helpUrl = 'https://www.mediawiki.org/wiki/API:Properties#templates_.2F_tl'; break; default: ApiBase::dieDebug( __METHOD__, 'Unknown module name' ); @@ -74,6 +76,10 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return + */ private function run( $resultPageSet = null ) { if ( $this->getPageSet()->getGoodTitleCount() == 0 ) { return; // nothing to do @@ -213,7 +219,7 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { ); if ( $this->getModuleName() == self::LINKS ) { $arr[$this->titlesParam] = 'Only list links to these titles. Useful for checking whether a certain page links to a certain title.'; - } else if ( $this->getModuleName() == self::TEMPLATES ) { + } elseif ( $this->getModuleName() == self::TEMPLATES ) { $arr[$this->titlesParam] = 'Only list these templates. Useful for checking whether a certain page uses a certain template.'; } return $arr; @@ -234,7 +240,11 @@ class ApiQueryLinks extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return $this->helpUrl; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLinks.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryLinks.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 7d69ca39..1420e0a7 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -1,6 +1,6 @@ addFieldsIf( 'log_id', $this->fld_ids ); - $this->addFieldsIf( 'page_id', $this->fld_ids ); - $this->addFieldsIf( 'log_user', $this->fld_user ); - $this->addFieldsIf( 'user_name', $this->fld_user ); + $this->addFieldsIf( array( 'log_id', 'page_id' ), $this->fld_ids ); + $this->addFieldsIf( array( 'log_user', 'user_name' ), $this->fld_user ); $this->addFieldsIf( 'user_id', $this->fld_userid ); - $this->addFieldsIf( 'log_namespace', $this->fld_title || $this->fld_parsedcomment ); - $this->addFieldsIf( 'log_title', $this->fld_title || $this->fld_parsedcomment ); + $this->addFieldsIf( array( 'log_namespace', 'log_title' ), $this->fld_title || $this->fld_parsedcomment ); $this->addFieldsIf( 'log_comment', $this->fld_comment || $this->fld_parsedcomment ); $this->addFieldsIf( 'log_params', $this->fld_details ); @@ -114,13 +111,12 @@ class ApiQueryLogEvents extends ApiQueryBase { list( $type, $action ) = explode( '/', $params['action'] ); $this->addWhereFld( 'log_type', $type ); $this->addWhereFld( 'log_action', $action ); - } - else if ( !is_null( $params['type'] ) ) { + } elseif ( !is_null( $params['type'] ) ) { $this->addWhereFld( 'log_type', $params['type'] ); $index['logging'] = 'type_time'; } - $this->addWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] ); + $this->addTimestampWhereRange( 'log_timestamp', $params['dir'], $params['start'], $params['end'] ); $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); @@ -148,6 +144,22 @@ class ApiQueryLogEvents extends ApiQueryBase { $index['logging'] = is_null( $user ) ? 'page_time' : array( 'page_time', 'user_time' ); } + $prefix = $params['prefix']; + + if ( !is_null( $prefix ) ) { + global $wgMiserMode; + if ( $wgMiserMode ) { + $this->dieUsage( 'Prefix search disabled in Miser Mode', 'prefixsearchdisabled' ); + } + + $title = Title::newFromText( $prefix ); + if ( is_null( $title ) ) { + $this->dieUsage( "Bad title value '$prefix'", 'param_prefix' ); + } + $this->addWhereFld( 'log_namespace', $title->getNamespace() ); + $this->addWhere( 'log_title ' . $db->buildLike( $title->getDBkey(), $db->anyString() ) ); + } + $this->addOption( 'USE INDEX', $index ); // Paranoia: avoid brute force searches (bug 17342) @@ -160,6 +172,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $count = 0; $res = $this->select( __METHOD__ ); + $result = $this->getResult(); foreach ( $res as $row ) { if ( ++ $count > $limit ) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -171,25 +184,25 @@ class ApiQueryLogEvents extends ApiQueryBase { if ( !$vals ) { continue; } - $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals ); + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); if ( !$fit ) { $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->log_timestamp ) ); break; } } - $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' ); + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'item' ); } /** - * @static * @param $result ApiResult - * @param $vals - * @param $params - * @param $type + * @param $vals array + * @param $params string + * @param $type string + * @param $action string * @param $ts * @return array */ - public static function addLogParams( $result, &$vals, $params, $type, $ts ) { + public static function addLogParams( $result, &$vals, $params, $type, $action, $ts ) { $params = explode( "\n", $params ); switch ( $type ) { case 'move': @@ -219,11 +232,14 @@ class ApiQueryLogEvents extends ApiQueryBase { $params = null; break; case 'block': + if ( $action == 'unblock' ) { + break; + } $vals2 = array(); list( $vals2['duration'], $vals2['flags'] ) = $params; // Indefinite blocks have no expiry time - if ( Block::parseExpiryInput( $params[0] ) !== Block::infinity() ) { + if ( SpecialBlock::parseExpiryInput( $params[0] ) !== wfGetDB( DB_SLAVE )->getInfinity() ) { $vals2['expiry'] = wfTimestamp( TS_ISO_8601, strtotime( $params[0], wfTimestamp( TS_UNIX, $ts ) ) ); } @@ -268,8 +284,11 @@ class ApiQueryLogEvents extends ApiQueryBase { $vals['actionhidden'] = ''; } else { self::addLogParams( - $this->getResult(), $vals, - $row->log_params, $row->log_type, + $this->getResult(), + $vals, + $row->log_params, + $row->log_type, + $row->log_action, $row->log_timestamp ); } @@ -285,7 +304,7 @@ class ApiQueryLogEvents extends ApiQueryBase { if ( $this->fld_userid ) { $vals['userid'] = $row->user_id; } - + if ( !$row->log_user ) { $vals['anon'] = ''; } @@ -372,6 +391,7 @@ class ApiQueryLogEvents extends ApiQueryBase { ), 'user' => null, 'title' => null, + 'prefix' => null, 'tag' => null, 'limit' => array( ApiBase::PARAM_DFLT => 10, @@ -384,27 +404,29 @@ class ApiQueryLogEvents extends ApiQueryBase { } public function getParamDescription() { + $p = $this->getModulePrefix(); return array( 'prop' => array( 'Which properties to get', - ' ids - Adds the id of the log event', + ' ids - Adds the ID of the log event', ' title - Adds the title of the page for the log event', ' type - Adds the type of log event', ' user - Adds the user responsible for the log event', - ' userid - Adds the user id who was responsible for the log event', + ' userid - Adds the user ID who was responsible for the log event', ' timestamp - Adds the timestamp for the event', ' comment - Adds the comment of the event', ' parsedcomment - Adds the parsed comment of the event', ' details - Lists addtional details about the event', ' tags - Lists tags for the event', ), - 'type' => 'Filter log entries to only this type(s)', - 'action' => "Filter log actions to only this type. Overrides {$this->getModulePrefix()}type", + 'type' => 'Filter log entries to only this type', + 'action' => "Filter log actions to only this type. Overrides {$p}type", 'start' => 'The timestamp to start enumerating from', 'end' => 'The timestamp to end enumerating', - 'dir' => 'In which direction to enumerate', + 'dir' => $this->getDirectionDescription( $p ), 'user' => 'Filter entries to those made by the given user', 'title' => 'Filter entries to those related to a page', + 'prefix' => 'Filter entries that start with this prefix. Disabled in Miser Mode', 'limit' => 'How many total event entries to return', 'tag' => 'Only list event entries tagged with this tag', ); @@ -418,6 +440,8 @@ class ApiQueryLogEvents extends ApiQueryBase { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'param_user', 'info' => 'User name $user not found' ), array( 'code' => 'param_title', 'info' => 'Bad title value \'title\'' ), + array( 'code' => 'param_prefix', 'info' => 'Bad title value \'prefix\'' ), + array( 'code' => 'prefixsearchdisabled', 'info' => 'Prefix search disabled in Miser Mode' ), ) ); } @@ -427,7 +451,11 @@ class ApiQueryLogEvents extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Logevents'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryLogEvents.php 74535 2010-10-09 00:01:45Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryLogEvents.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryPageProps.php b/includes/api/ApiQueryPageProps.php index 894e812d..64b8511d 100644 --- a/includes/api/ApiQueryPageProps.php +++ b/includes/api/ApiQueryPageProps.php @@ -1,6 +1,6 @@ params = $this->extractRequestParams(); - # Only operate on existing pages $pages = $this->getPageSet()->getGoodTitles(); if ( !count( $pages ) ) { # Nothing to do return; } - + + $this->params = $this->extractRequestParams(); + $this->addTables( 'page_props' ); $this->addFields( array( 'pp_page', 'pp_propname', 'pp_value' ) ); $this->addWhereFld( 'pp_page', array_keys( $pages ) ); - + if ( $this->params['continue'] ) { $this->addWhere( 'pp_page >=' . intval( $this->params['continue'] ) ); } - + + if ( $this->params['prop'] ) { + $this->addWhereFld( 'pp_propname', $this->params['prop'] ); + } + # Force a sort order to ensure that properties are grouped by page $this->addOption( 'ORDER BY', 'pp_page' ); - + $res = $this->select( __METHOD__ ); $currentPage = 0; # Id of the page currently processed $props = array(); $result = $this->getResult(); - + foreach ( $res as $row ) { if ( $currentPage != $row->pp_page ) { - # Different page than previous row, so add the properties to + # Different page than previous row, so add the properties to # the result and save the new page id - + if ( $currentPage ) { if ( !$this->addPageProps( $result, $currentPage, $props ) ) { # addPageProps() indicated that the result did not fit # so stop adding data. Reset props so that it doesn't # get added again after loop exit - + $props = array(); break; } - + $props = array(); } - + $currentPage = $row->pp_page; } - + $props[$row->pp_propname] = $row->pp_value; } - + if ( count( $props ) ) { # Add any remaining properties to the results $this->addPageProps( $result, $currentPage, $props ); @@ -99,7 +103,7 @@ class ApiQueryPageProps extends ApiQueryBase { } /** - * Add page properties to an ApiResult, adding a continue + * Add page properties to an ApiResult, adding a continue * parameter if it doesn't fit. * * @param $result ApiResult @@ -109,7 +113,7 @@ class ApiQueryPageProps extends ApiQueryBase { */ private function addPageProps( $result, $page, $props ) { $fit = $result->addValue( array( 'query', 'pages', $page ), 'pageprops', $props ); - + if ( !$fit ) { $this->setContinueEnumParameter( 'continue', $page ); } @@ -120,31 +124,35 @@ class ApiQueryPageProps extends ApiQueryBase { return 'public'; } - public function getAllowedParams() { - return array( 'continue' => null ); + public function getAllowedParams() { + return array( + 'continue' => null, + 'prop' => null, + ); } public function getParamDescription() { - return array( 'continue' => 'When more results are available, use this to continue' ); + return array( + 'continue' => 'When more results are available, use this to continue', + 'prop' => 'Page prop to look on the page for. Useful for checking whether a certain page uses a certain page prop.' + ); } public function getDescription() { return 'Get various properties defined in the page content'; } - public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'code' => '_badcontinue', 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' ), - ) ); - } - protected function getExamples() { return array( 'api.php?action=query&prop=pageprops&titles=Category:Foo', ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#pageprops_.2F_pp'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryPageProps.php 85211 2011-04-02 21:01:00Z demon $'; + return __CLASS__ . ': $Id: ApiQueryPageProps.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php index e647c39f..14df7446 100644 --- a/includes/api/ApiQueryProtectedTitles.php +++ b/includes/api/ApiQueryProtectedTitles.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2009 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -48,6 +48,10 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); @@ -60,7 +64,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { $this->addFieldsIf( 'pt_expiry', isset( $prop['expiry'] ) ); $this->addFieldsIf( 'pt_create_perm', isset( $prop['level'] ) ); - $this->addWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] ); + $this->addTimestampWhereRange( 'pt_timestamp', $params['dir'], $params['start'], $params['end'] ); $this->addWhereFld( 'pt_namespace', $params['namespace'] ); $this->addWhereFld( 'pt_create_perm', $params['level'] ); @@ -77,6 +81,9 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { $count = 0; $result = $this->getResult(); + + $titles = array(); + foreach ( $res as $row ) { if ( ++ $count > $params['limit'] ) { // We've reached the one extra which shows that there are additional pages to be had. Stop here... @@ -110,7 +117,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { } if ( isset( $prop['expiry'] ) ) { - $vals['expiry'] = Block::decodeExpiry( $row->pt_expiry, TS_ISO_8601 ); + global $wgContLang; + $vals['expiry'] = $wgContLang->formatExpiry( $row->pt_expiry, TS_ISO_8601 ); } if ( isset( $prop['level'] ) ) { @@ -165,8 +173,8 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { 'dir' => array( ApiBase::PARAM_DFLT => 'older', ApiBase::PARAM_TYPE => array( - 'older', - 'newer' + 'newer', + 'older' ) ), 'start' => array( @@ -196,13 +204,13 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { 'namespace' => 'Only list titles in these namespaces', 'start' => 'Start listing at this protection timestamp', 'end' => 'Stop listing at this protection timestamp', - 'dir' => 'The direction in which to list', + 'dir' => $this->getDirectionDescription( $this->getModulePrefix() ), 'limit' => 'How many total pages to return', 'prop' => array( 'Which properties to get', ' timestamp - Adds the timestamp of when protection was added', - ' user - Adds the user to add the protection', - ' userid - Adds the user id to add the protection', + ' user - Adds the user that added the protection', + ' userid - Adds the user id that added the protection', ' comment - Adds the comment for the protection', ' parsedcomment - Adds the parsed comment for the protection', ' expiry - Adds the timestamp of when the protection will be lifted', @@ -222,7 +230,11 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Protectedtitles'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 71838 2010-08-28 01:18:18Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryProtectedTitles.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php new file mode 100644 index 00000000..e22cf8eb --- /dev/null +++ b/includes/api/ApiQueryQueryPage.php @@ -0,0 +1,198 @@ +.@gmail.com + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +if ( !defined( 'MEDIAWIKI' ) ) { + // Eclipse helper - will be ignored in production + require_once( 'ApiQueryBase.php' ); +} + +/** + * Query module to get the results of a QueryPage-based special page + * + * @ingroup API + */ +class ApiQueryQueryPage extends ApiQueryGeneratorBase { + private $qpMap; + + /** + * Some query pages are useless because they're available elsewhere in the API + */ + private $uselessQueryPages = array( + 'MIMEsearch', // aiprop=mime + 'LinkSearch', // list=exturlusage + 'FileDuplicateSearch', // prop=duplicatefiles + ); + + public function __construct( $query, $moduleName ) { + parent::__construct( $query, $moduleName, 'qp' ); + // We need to do this to make sure $wgQueryPages is set up + // This SUCKS + global $IP; + require_once( "$IP/includes/QueryPage.php" ); + + // Build mapping from special page names to QueryPage classes + global $wgQueryPages; + $this->qpMap = array(); + foreach ( $wgQueryPages as $page ) { + if( !in_array( $page[1], $this->uselessQueryPages ) ) { + $this->qpMap[$page[1]] = $page[0]; + } + } + } + + public function execute() { + $this->run(); + } + + public function executeGenerator( $resultPageSet ) { + $this->run( $resultPageSet ); + } + + /** + * @param $resultPageSet ApiPageSet + * @return void + */ + public function run( $resultPageSet = null ) { + global $wgUser; + $params = $this->extractRequestParams(); + $result = $this->getResult(); + + $qp = new $this->qpMap[$params['page']](); + if ( !$qp->userCanExecute( $wgUser ) ) { + $this->dieUsageMsg( 'specialpage-cantexecute' ); + } + + $r = array( 'name' => $params['page'] ); + if ( $qp->isCached() ) { + if ( !$qp->isCacheable() ) { + $r['disabled'] = ''; + } else { + $r['cached'] = ''; + $ts = $qp->getCachedTimestamp(); + if ( $ts ) { + $r['cachedtimestamp'] = wfTimestamp( TS_ISO_8601, $ts ); + } + } + } + $result->addValue( array( 'query' ), $this->getModuleName(), $r ); + + if ( $qp->isCached() && !$qp->isCacheable() ) { + // Disabled query page, don't run the query + return; + } + + $res = $qp->doQuery( $params['offset'], $params['limit'] + 1 ); + $count = 0; + $titles = array(); + foreach ( $res as $row ) { + if ( ++$count > $params['limit'] ) { + // We've had enough + $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] ); + break; + } + + $title = Title::makeTitle( $row->namespace, $row->title ); + if ( is_null( $resultPageSet ) ) { + $data = array( 'value' => $row->value ); + if ( $qp->usesTimestamps() ) { + $data['timestamp'] = wfTimestamp( TS_ISO_8601, $row->value ); + } + self::addTitleInfo( $data, $title ); + + foreach ( $row as $field => $value ) { + if ( !in_array( $field, array( 'namespace', 'title', 'value', 'qc_type' ) ) ) { + $data['databaseResult'][$field] = $value; + } + } + + $fit = $result->addValue( array( 'query', $this->getModuleName(), 'results' ), null, $data ); + if ( !$fit ) { + $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 ); + break; + } + } else { + $titles[] = $title; + } + } + if ( is_null( $resultPageSet ) ) { + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName(), 'results' ), 'page' ); + } else { + $resultPageSet->populateFromTitles( $titles ); + } + } + + public function getCacheMode( $params ) { + $qp = new $this->qpMap[$params['page']](); + if ( $qp->getRestriction() != '' ) { + return 'private'; + } + return 'public'; + } + + public function getAllowedParams() { + return array( + 'page' => array( + ApiBase::PARAM_TYPE => array_keys( $this->qpMap ), + ApiBase::PARAM_REQUIRED => true + ), + 'offset' => 0, + 'limit' => array( + ApiBase::PARAM_DFLT => 10, + ApiBase::PARAM_TYPE => 'limit', + ApiBase::PARAM_MIN => 1, + ApiBase::PARAM_MAX => ApiBase::LIMIT_BIG1, + ApiBase::PARAM_MAX2 => ApiBase::LIMIT_BIG2 + ), + ); + } + + public function getParamDescription() { + return array( + 'page' => 'The name of the special page. Note, this is case sensitive', + 'offset' => 'When more results are available, use this to continue', + 'limit' => 'Number of results to return', + ); + } + + public function getDescription() { + return 'Get a list provided by a QueryPage-based special page'; + } + + public function getPossibleErrors() { + return array_merge( parent::getPossibleErrors(), array( + ) ); + } + + protected function getExamples() { + return array( + 'api.php?action=query&list=querypage&qppage=Ancientpages' + ); + } + + public function getVersion() { + return __CLASS__ . ': $Id: ApiQueryQueryPage.php 99989 2011-10-16 22:24:58Z reedy $'; + } +} diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php index b3b840fd..dea0b0f5 100644 --- a/includes/api/ApiQueryRandom.php +++ b/includes/api/ApiQueryRandom.php @@ -1,7 +1,7 @@ run( $resultPageSet ); } + /** + * @param $randstr + * @param $limit + * @param $namespace + * @param $resultPageSet ApiPageSet + * @param $redirect + * @return void + */ protected function prepareQuery( $randstr, $limit, $namespace, &$resultPageSet, $redirect ) { $this->resetQueryParams(); $this->addTables( 'page' ); @@ -65,7 +73,11 @@ if ( !defined( 'MEDIAWIKI' ) ) { } } - protected function runQuery( &$resultPageSet ) { + /** + * @param $resultPageSet ApiPageSet + * @return int + */ + protected function runQuery( $resultPageSet = null ) { $res = $this->select( __METHOD__ ); $count = 0; foreach ( $res as $row ) { @@ -92,6 +104,10 @@ if ( !defined( 'MEDIAWIKI' ) ) { return $count; } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ public function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); $result = $this->getResult(); diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index fb0d42b8..b144a7cf 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -1,6 +1,6 @@ tokenFunctions; } + /** + * @param $pageid + * @param $title + * @param $rc RecentChange + * @return bool|String + */ public static function getPatrolToken( $pageid, $title, $rc ) { global $wgUser; if ( !$wgUser->useRCPatrol() && ( !$wgUser->useNPPatrol() || @@ -84,7 +91,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { if ( is_null( $cachedPatrolToken ) ) { $cachedPatrolToken = $wgUser->editToken( 'patrol' ); } - + return $cachedPatrolToken; } @@ -108,10 +115,20 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->fld_tags = isset( $prop['tags'] ); } + public function execute() { + $this->run(); + } + + public function executeGenerator( $resultPageSet ) { + $this->run( $resultPageSet ); + } + /** * Generates and outputs the result of this query based upon the provided parameters. + * + * @param $resultPageSet ApiPageSet */ - public function execute() { + public function run( $resultPageSet = null ) { global $wgUser; /* Get the parameters of the request. */ $params = $this->extractRequestParams(); @@ -123,7 +140,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { */ $this->addTables( 'recentchanges' ); $index = array( 'recentchanges' => 'rc_timestamp' ); // May change - $this->addWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] ); + $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] ); $this->addWhereFld( 'rc_namespace', $params['namespace'] ); $this->addWhereFld( 'rc_deleted', 0 ); @@ -140,9 +157,8 @@ class ApiQueryRecentChanges extends ApiQueryBase { || ( isset( $show['anon'] ) && isset( $show['!anon'] ) ) || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) ) || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) - ) - { - $this->dieUsageMsg( array( 'show' ) ); + ) { + $this->dieUsageMsg( 'show' ); } // Check permissions @@ -195,6 +211,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'rc_deleted' ) ); + $showRedirects = false; /* Determine what properties we need to display. */ if ( !is_null( $params['prop'] ) ) { $prop = array_flip( $params['prop'] ); @@ -207,27 +224,15 @@ class ApiQueryRecentChanges extends ApiQueryBase { } /* Add fields to our query if they are specified as a needed parameter. */ - $this->addFieldsIf( 'rc_id', $this->fld_ids ); - $this->addFieldsIf( 'rc_this_oldid', $this->fld_ids ); - $this->addFieldsIf( 'rc_last_oldid', $this->fld_ids ); + $this->addFieldsIf( array( 'rc_id', 'rc_this_oldid', 'rc_last_oldid' ), $this->fld_ids ); $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment ); $this->addFieldsIf( 'rc_user', $this->fld_user ); $this->addFieldsIf( 'rc_user_text', $this->fld_user || $this->fld_userid ); - $this->addFieldsIf( 'rc_minor', $this->fld_flags ); - $this->addFieldsIf( 'rc_bot', $this->fld_flags ); - $this->addFieldsIf( 'rc_new', $this->fld_flags ); - $this->addFieldsIf( 'rc_old_len', $this->fld_sizes ); - $this->addFieldsIf( 'rc_new_len', $this->fld_sizes ); + $this->addFieldsIf( array( 'rc_minor', 'rc_new', 'rc_bot' ) , $this->fld_flags ); + $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes ); $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); - $this->addFieldsIf( 'rc_logid', $this->fld_loginfo ); - $this->addFieldsIf( 'rc_log_type', $this->fld_loginfo ); - $this->addFieldsIf( 'rc_log_action', $this->fld_loginfo ); - $this->addFieldsIf( 'rc_params', $this->fld_loginfo ); - if ( $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] ) ) { - $this->addTables( 'page' ); - $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) ); - $this->addFields( 'page_is_redirect' ); - } + $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo ); + $showRedirects = $this->fld_redirect || isset( $show['redirect'] ) || isset( $show['!redirect'] ); } if ( $this->fld_tags ) { @@ -236,6 +241,16 @@ class ApiQueryRecentChanges extends ApiQueryBase { $this->addFields( 'ts_tags' ); } + if ( $params['toponly'] || $showRedirects ) { + $this->addTables( 'page' ); + $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) ); + $this->addFields( 'page_is_redirect' ); + + if ( $params['toponly'] ) { + $this->addWhere( 'rc_this_oldid = page_latest' ); + } + } + if ( !is_null( $params['tag'] ) ) { $this->addTables( 'change_tag' ); $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) ); @@ -252,6 +267,10 @@ class ApiQueryRecentChanges extends ApiQueryBase { /* Perform the actual query. */ $res = $this->select( __METHOD__ ); + $titles = array(); + + $result = $this->getResult(); + /* Iterate through the rows, adding data extracted from them to our query result. */ foreach ( $res as $row ) { if ( ++ $count > $params['limit'] ) { @@ -260,22 +279,30 @@ class ApiQueryRecentChanges extends ApiQueryBase { break; } - /* Extract the data from a single row. */ - $vals = $this->extractRowInfo( $row ); + if ( is_null( $resultPageSet ) ) { + /* Extract the data from a single row. */ + $vals = $this->extractRowInfo( $row ); - /* Add that row's data to our final output. */ - if ( !$vals ) { - continue; - } - $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), null, $vals ); - if ( !$fit ) { - $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) ); - break; + /* Add that row's data to our final output. */ + if ( !$vals ) { + continue; + } + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); + if ( !$fit ) { + $this->setContinueEnumParameter( 'start', wfTimestamp( TS_ISO_8601, $row->rc_timestamp ) ); + break; + } + } else { + $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title ); } } - /* Format the result */ - $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' ); + if ( is_null( $resultPageSet ) ) { + /* Format the result */ + $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'rc' ); + } else { + $resultPageSet->populateFromTitles( $titles ); + } } /** @@ -288,8 +315,7 @@ class ApiQueryRecentChanges extends ApiQueryBase { public function extractRowInfo( $row ) { /* If page was moved somewhere, get the title of the move target. */ $movedToTitle = false; - if ( isset( $row->rc_moved_to_title ) && $row->rc_moved_to_title !== '' ) - { + if ( isset( $row->rc_moved_to_title ) && $row->rc_moved_to_title !== '' ) { $movedToTitle = Title::makeTitle( $row->rc_moved_to_ns, $row->rc_moved_to_title ); } @@ -405,8 +431,11 @@ class ApiQueryRecentChanges extends ApiQueryBase { $vals['logaction'] = $row->rc_log_action; ApiQueryLogEvents::addLogParams( $this->getResult(), - $vals, $row->rc_params, - $row->rc_log_type, $row->rc_timestamp + $vals, + $row->rc_params, + $row->rc_log_action, + $row->rc_log_type, + $row->rc_timestamp ); } @@ -550,15 +579,17 @@ class ApiQueryRecentChanges extends ApiQueryBase { 'new', 'log' ) - ) + ), + 'toponly' => false, ); } public function getParamDescription() { + $p = $this->getModulePrefix(); return array( 'start' => 'The timestamp to start enumerating from', 'end' => 'The timestamp to end enumerating', - 'dir' => 'In which direction to enumerate', + 'dir' => $this->getDirectionDescription( $p ), 'namespace' => 'Filter log entries to only this namespace(s)', 'user' => 'Only list changes by this user', 'excludeuser' => 'Don\'t list changes by this user', @@ -571,21 +602,22 @@ class ApiQueryRecentChanges extends ApiQueryBase { ' flags - Adds flags for the edit', ' timestamp - Adds timestamp of the edit', ' title - Adds the page title of the edit', - ' ids - Adds the page id, recent changes id and the new and old revision id', + ' ids - Adds the page ID, recent changes ID and the new and old revision ID', ' sizes - Adds the new and old page length in bytes', ' redirect - Tags edit if page is a redirect', - ' patrolled - Tags edits have have been patrolled', + ' patrolled - Tags edits that have been patrolled', ' loginfo - Adds log information (logid, logtype, etc) to log entries', ' tags - Lists tags for the entry', ), 'token' => 'Which tokens to obtain for each change', 'show' => array( 'Show only items that meet this criteria.', - "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}show=minor|!anon" + "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon" ), 'type' => 'Which types of changes to show', 'limit' => 'How many total changes to return', 'tag' => 'Only list changes tagged with this tag', + 'toponly' => 'Only list changes which are the latest revision', ); } @@ -607,7 +639,11 @@ class ApiQueryRecentChanges extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Recentchanges'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 78437 2010-12-15 14:14:16Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryRecentChanges.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 64a0a4ea..e6b21a92 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -1,6 +1,6 @@ tokenFunctions; } + /** + * @param $pageid + * @param $title Title + * @param $rev Revision + * @return bool|String + */ public static function getRollbackToken( $pageid, $title, $rev ) { global $wgUser; if ( !$wgUser->isAllowed( 'rollback' ) ) { @@ -119,8 +125,7 @@ class ApiQueryRevisions extends ApiQueryBase { $params['diffto'] = 0; } if ( ( !ctype_digit( $params['diffto'] ) || $params['diffto'] < 0 ) - && $params['diffto'] != 'prev' && $params['diffto'] != 'next' ) - { + && $params['diffto'] != 'prev' && $params['diffto'] != 'next' ) { $this->dieUsage( 'rvdiffto must be set to a non-negative number, "prev", "next" or "cur"', 'diffto' ); } // Check whether the revision exists and is readable, @@ -169,7 +174,6 @@ class ApiQueryRevisions extends ApiQueryBase { $this->getResult()->setParsedLimit( $this->getModuleName(), $limit ); } - if ( !is_null( $this->token ) || $pageCount > 0 ) { $this->addFields( Revision::selectPageFields() ); } @@ -224,10 +228,9 @@ class ApiQueryRevisions extends ApiQueryBase { } } - //Bug 24166 - API error when using rvprop=tags + // Bug 24166 - API error when using rvprop=tags $this->addTables( 'revision' ); - if ( $enumRevMode ) { // This is mostly to prevent parameter errors (and optimize SQL?) if ( !is_null( $params['startid'] ) && !is_null( $params['start'] ) ) { @@ -249,14 +252,14 @@ class ApiQueryRevisions extends ApiQueryBase { // one row with the same timestamp for the same page. // The order needs to be the same as start parameter to avoid SQL filesort. if ( is_null( $params['startid'] ) && is_null( $params['endid'] ) ) { - $this->addWhereRange( 'rev_timestamp', $params['dir'], + $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'], $params['start'], $params['end'] ); } else { $this->addWhereRange( 'rev_id', $params['dir'], $params['startid'], $params['endid'] ); // One of start and end can be set // If neither is set, this does nothing - $this->addWhereRange( 'rev_timestamp', $params['dir'], + $this->addTimestampWhereRange( 'rev_timestamp', $params['dir'], $params['start'], $params['end'], false ); } @@ -479,27 +482,7 @@ class ApiQueryRevisions extends ApiQueryBase { $text = $wgParser->preprocess( $text, $title, new ParserOptions() ); } if ( $this->parseContent ) { - global $wgEnableParserCache; - - $popts = new ParserOptions(); - $popts->setTidy( true ); - - $articleObj = new Article( $title ); - - $p_result = false; - $pcache = ParserCache::singleton(); - if ( $wgEnableParserCache ) { - $p_result = $pcache->get( $articleObj, $popts ); - } - if ( !$p_result ) { - $p_result = $wgParser->parse( $text, $title, $popts ); - - if ( $wgEnableParserCache ) { - $pcache->save( $p_result, $articleObj, $popts ); - } - } - - $text = $p_result->getText(); + $text = $wgParser->parse( $text, $title, new ParserOptions() )->getText(); } ApiResult::setContent( $vals, $text ); } elseif ( $this->fld_content ) { @@ -627,9 +610,9 @@ class ApiQueryRevisions extends ApiQueryBase { 'endid' => 'Stop revision enumeration on this revid (enum)', 'start' => 'From which revision timestamp to start enumeration (enum)', 'end' => 'Enumerate up to this timestamp (enum)', - 'dir' => 'Direction of enumeration - towards "newer" or "older" revisions (enum)', - 'user' => 'Only include revisions made by user', - 'excludeuser' => 'Exclude revisions made by user', + 'dir' => $this->getDirectionDescription( $p, ' (enum)' ), + 'user' => 'Only include revisions made by user (enum)', + 'excludeuser' => 'Exclude revisions made by user (enum)', 'expandtemplates' => 'Expand templates in revision content', 'generatexml' => 'Generate XML parse tree for revision content', 'parse' => 'Parse revision content. For performance reasons if this option is used, rvlimit is enforced to 1.', @@ -647,7 +630,7 @@ class ApiQueryRevisions extends ApiQueryBase { public function getDescription() { return array( 'Get revision information', - 'This module may be used in several ways:', + 'May be used in several ways:', ' 1) Get data about a set of pages (last revision), by setting titles or pageids parameter', ' 2) Get revisions for one given page, by using titles/pageids with start/end/limit params', ' 3) Get data about a set of revisions by setting their IDs with revids parameter', @@ -685,7 +668,11 @@ class ApiQueryRevisions extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Properties#revisions_.2F_rv'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryRevisions.php 75521 2010-10-27 11:50:20Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryRevisions.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 3cf693af..42bed93a 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -1,6 +1,6 @@ run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { global $wgContLang; $params = $this->extractRequestParams(); @@ -93,16 +97,17 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" ); } + $apiResult = $this->getResult(); // Add search meta data to result if ( isset( $searchInfo['totalhits'] ) ) { $totalhits = $matches->getTotalHits(); if ( $totalhits !== null ) { - $this->getResult()->addValue( array( 'query', 'searchinfo' ), + $apiResult->addValue( array( 'query', 'searchinfo' ), 'totalhits', $totalhits ); } } if ( isset( $searchInfo['suggestion'] ) && $matches->hasSuggestion() ) { - $this->getResult()->addValue( array( 'query', 'searchinfo' ), + $apiResult->addValue( array( 'query', 'searchinfo' ), 'suggestion', $matches->getSuggestionQuery() ); } @@ -110,7 +115,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $terms = $wgContLang->convertForSearchResult( $matches->termMatches() ); $titles = array(); $count = 0; - while ( $result = $matches->next() ) { + $result = $matches->next(); + + while ( $result ) { if ( ++ $count > $limit ) { // We've reached the one extra which shows that there are additional items to be had. Stop here... $this->setContinueEnumParameter( 'offset', $params['offset'] + $params['limit'] ); @@ -119,6 +126,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { // Silently skip broken and missing titles if ( $result->isBrokenTitle() || $result->isMissingRevision() ) { + $result = $matches->next(); continue; } @@ -155,7 +163,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } if ( !is_null( $result->getSectionTitle() ) ) { if ( isset( $prop['sectiontitle'] ) ) { - $vals['sectiontitle'] = $result->getSectionTitle(); + $vals['sectiontitle'] = $result->getSectionTitle()->getFragment(); } if ( isset( $prop['sectionsnippet'] ) ) { $vals['sectionsnippet'] = $result->getSectionSnippet(); @@ -166,7 +174,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } // Add item to results and see whether it fits - $fit = $this->getResult()->addValue( array( 'query', $this->getModuleName() ), + $fit = $apiResult->addValue( array( 'query', $this->getModuleName() ), null, $vals ); if ( !$fit ) { $this->setContinueEnumParameter( 'offset', $params['offset'] + $count - 1 ); @@ -175,10 +183,12 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } else { $titles[] = $title; } + + $result = $matches->next(); } if ( is_null( $resultPageSet ) ) { - $this->getResult()->setIndexedTagName_internal( array( + $apiResult->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'p' ); } else { @@ -260,10 +270,10 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ' score - Adds the score (if any) from the search engine', ' snippet - Adds a parsed snippet of the page', ' titlesnippet - Adds a parsed snippet of the page title', - ' redirectsnippet - Adds a parsed snippet of the redirect', - ' redirecttitle - Adds a parsed snippet of the redirect title', - ' sectionsnippet - Adds a parsed snippet of the matching section', - ' sectiontitle - Adds a parsed snippet of the matching section title', + ' redirectsnippet - Adds a parsed snippet of the redirect title', + ' redirecttitle - Adds the title of the matching redirect', + ' sectionsnippet - Adds a parsed snippet of the matching section title', + ' sectiontitle - Adds the title of the matching section', ' hasrelated - Indicates whether a related search is available', ), 'redirects' => 'Include redirect pages in the search', @@ -291,7 +301,11 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Search'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySearch.php 76300 2010-11-08 12:23:24Z reedy $'; + return __CLASS__ . ': $Id: ApiQuerySearch.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 379a4228..b6cedc6c 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -1,6 +1,6 @@ appendLanguages( $p ); break; + case 'skins': + $fit = $this->appendSkins( $p ); + break; + case 'extensiontags': + $fit = $this->appendExtensionTags( $p ); + break; + case 'functionhooks': + $fit = $this->appendFunctionHooks( $p ); + break; + case 'showhooks': + $fit = $this->appendSubscribedHooks( $p ); + break; default: ApiBase::dieDebug( __METHOD__, "Unknown prop=$p" ); } @@ -105,7 +117,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data = array(); $mainPage = Title::newMainPage(); $data['mainpage'] = $mainPage->getPrefixedText(); - $data['base'] = $mainPage->getFullUrl(); + $data['base'] = wfExpandUrl( $mainPage->getFullUrl(), PROTO_CURRENT ); $data['sitename'] = $GLOBALS['wgSitename']; $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}"; $data['phpversion'] = phpversion(); @@ -157,6 +169,12 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['wikiid'] = wfWikiID(); $data['time'] = wfTimestamp( TS_ISO_8601, time() ); + if ( $GLOBALS['wgMiserMode'] ) { + $data['misermode'] = ''; + } + + wfRunHooks( 'APIQuerySiteInfoGeneralInfo', array( $this, &$data ) ); + return $this->getResult()->addValue( 'query', $property, $data ); } @@ -212,8 +230,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function appendSpecialPageAliases( $property ) { global $wgContLang; $data = array(); - foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) - { + foreach ( $wgContLang->getSpecialPageAliases() as $specialpage => $aliases ) { $arr = array( 'realname' => $specialpage, 'aliases' => $aliases ); $this->getResult()->setIndexedTagName( $arr['aliases'], 'alias' ); $data[] = $arr; @@ -241,7 +258,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function appendInterwikiMap( $property, $filter ) { $this->resetQueryParams(); $this->addTables( 'interwiki' ); - $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url' ) ); + $this->addFields( array( 'iw_prefix', 'iw_local', 'iw_url', 'iw_wikiid', 'iw_api' ) ); if ( $filter === 'local' ) { $this->addWhere( 'iw_local = 1' ); @@ -267,7 +284,13 @@ class ApiQuerySiteinfo extends ApiQueryBase { if ( isset( $langNames[$row->iw_prefix] ) ) { $val['language'] = $langNames[$row->iw_prefix]; } - $val['url'] = $row->iw_url; + $val['url'] = wfExpandUrl( $row->iw_url, PROTO_CURRENT ); + if( isset( $row->iw_wikiid ) ) { + $val['wikiid'] = $row->iw_wikiid; + } + if( isset( $row->iw_api ) ) { + $val['api'] = $row->iw_api; + } $data[] = $val; } @@ -279,12 +302,12 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function appendDbReplLagInfo( $property, $includeAll ) { global $wgShowHostnames; $data = array(); + $lb = wfGetLB(); if ( $includeAll ) { if ( !$wgShowHostnames ) { $this->dieUsage( 'Cannot view all servers info unless $wgShowHostnames is true', 'includeAllDenied' ); } - $lb = wfGetLB(); $lags = $lb->getLagTimes(); foreach ( $lags as $i => $lag ) { $data[] = array( @@ -293,9 +316,11 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); } } else { - list( $host, $lag ) = wfGetLB()->getMaxLag(); + list( $host, $lag, $index ) = $lb->getMaxLag(); $data[] = array( - 'host' => $wgShowHostnames ? $host : '', + 'host' => $wgShowHostnames + ? $lb->getServerName( $index ) + : '', 'lag' => intval( $lag ) ); } @@ -324,46 +349,47 @@ class ApiQuerySiteinfo extends ApiQueryBase { protected function appendUserGroups( $property, $numberInGroup ) { global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; - + $data = array(); + $result = $this->getResult(); foreach ( $wgGroupPermissions as $group => $permissions ) { $arr = array( 'name' => $group, 'rights' => array_keys( $permissions, true ), ); - + if ( $numberInGroup ) { global $wgAutopromote; - + if ( $group == 'user' ) { $arr['number'] = SiteStats::users(); - + // '*' and autopromote groups have no size } elseif ( $group !== '*' && !isset( $wgAutopromote[$group] ) ) { $arr['number'] = SiteStats::numberInGroup( $group ); } } - + $groupArr = array( 'add' => $wgAddGroups, 'remove' => $wgRemoveGroups, 'add-self' => $wgGroupsAddToSelf, 'remove-self' => $wgGroupsRemoveFromSelf ); - - foreach( $groupArr as $type => $rights ) { - if( isset( $rights[$group] ) ) { + + foreach ( $groupArr as $type => $rights ) { + if ( isset( $rights[$group] ) ) { $arr[$type] = $rights[$group]; - $this->getResult()->setIndexedTagName( $arr[$type], 'group' ); + $result->setIndexedTagName( $arr[$type], 'group' ); } } - - $this->getResult()->setIndexedTagName( $arr['rights'], 'permission' ); + + $result->setIndexedTagName( $arr['rights'], 'permission' ); $data[] = $arr; } - - $this->getResult()->setIndexedTagName( $data, 'group' ); - return $this->getResult()->addValue( 'query', $property, $data ); + + $result->setIndexedTagName( $data, 'group' ); + return $result->addValue( 'query', $property, $data ); } protected function appendFileExtensions( $property ) { @@ -423,11 +449,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $this->getResult()->addValue( 'query', $property, $data ); } - protected function appendRightsInfo( $property ) { global $wgRightsPage, $wgRightsUrl, $wgRightsText; $title = Title::newFromText( $wgRightsPage ); - $url = $title ? $title->getFullURL() : $wgRightsUrl; + $url = $title ? wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ) : $wgRightsUrl; $text = $wgRightsText; if ( !$text && $title ) { $text = $title->getPrefixedText(); @@ -452,6 +477,57 @@ class ApiQuerySiteinfo extends ApiQueryBase { return $this->getResult()->addValue( 'query', $property, $data ); } + public function appendSkins( $property ) { + $data = array(); + foreach ( Skin::getSkinNames() as $name => $displayName ) { + $skin = array( 'code' => $name ); + ApiResult::setContent( $skin, $displayName ); + $data[] = $skin; + } + $this->getResult()->setIndexedTagName( $data, 'skin' ); + return $this->getResult()->addValue( 'query', $property, $data ); + } + + public function appendExtensionTags( $property ) { + global $wgParser; + $wgParser->firstCallInit(); + $tags = array_map( array( $this, 'formatParserTags'), $wgParser->getTags() ); + $this->getResult()->setIndexedTagName( $tags, 't' ); + return $this->getResult()->addValue( 'query', $property, $tags ); + } + + public function appendFunctionHooks( $property ) { + global $wgParser; + $wgParser->firstCallInit(); + $hooks = $wgParser->getFunctionHooks(); + $this->getResult()->setIndexedTagName( $hooks, 'h' ); + return $this->getResult()->addValue( 'query', $property, $hooks ); + } + + private function formatParserTags( $item ) { + return "<{$item}>"; + } + + public function appendSubscribedHooks( $property ) { + global $wgHooks; + $myWgHooks = $wgHooks; + ksort( $myWgHooks ); + + $data = array(); + foreach ( $myWgHooks as $hook => $hooks ) { + $arr = array( + 'name' => $hook, + 'subscribers' => array_map( array( 'SpecialVersion', 'arrayToString' ), $hooks ), + ); + + $this->getResult()->setIndexedTagName( $arr['subscribers'], 's' ); + $data[] = $arr; + } + + $this->getResult()->setIndexedTagName( $data, 'hook' ); + return $this->getResult()->addValue( 'query', $property, $data ); + } + public function getCacheMode( $params ) { return 'public'; } @@ -475,6 +551,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { 'fileextensions', 'rightsinfo', 'languages', + 'skins', + 'extensiontags', + 'functionhooks', + 'showhooks', ) ), 'filteriw' => array( @@ -505,6 +585,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { ' fileextensions - Returns list of file extensions allowed to be uploaded', ' rightsinfo - Returns wiki rights (license) information if available', ' languages - Returns a list of languages MediaWiki supports', + ' skins - Returns a list of all enabled skins', + ' extensiontags - Returns a list of parser extension tags', + ' functionhooks - Returns a list of parser function hooks', + ' showhooks - Returns a list of all subscribed hooks (contents of $wgHooks)' ), 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', 'showalldb' => 'List all database servers, not just the one lagging the most', @@ -530,7 +614,11 @@ class ApiQuerySiteinfo extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Meta#siteinfo_.2F_si'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiQuerySiteinfo.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryStashImageInfo.php b/includes/api/ApiQueryStashImageInfo.php index 769b3e9d..9a6e8530 100644 --- a/includes/api/ApiQueryStashImageInfo.php +++ b/includes/api/ApiQueryStashImageInfo.php @@ -38,58 +38,53 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo { $prop = array_flip( $params['prop'] ); $scale = $this->getScale( $params ); - + $result = $this->getResult(); - + + if ( !$params['filekey'] && !$params['sessionkey'] ) { + $this->dieUsage( "One of filekey or sessionkey must be supplied", 'nofilekey'); + } + try { $stash = RepoGroup::singleton()->getLocalRepo()->getUploadStash(); - - foreach ( $params['sessionkey'] as $sessionkey ) { - $file = $stash->getFile( $sessionkey ); - $imageInfo = self::getInfo( $file, $prop, $result, $scale ); + + foreach ( $params['filekey'] as $filekey ) { + $file = $stash->getFile( $filekey ); + $finalThumbParam = $this->mergeThumbParams( $file, $scale, $params['urlparam'] ); + $imageInfo = ApiQueryImageInfo::getInfo( $file, $prop, $result, $finalThumbParam ); $result->addValue( array( 'query', $this->getModuleName() ), null, $imageInfo ); $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), $modulePrefix ); } - + //TODO: update exception handling here to understand current getFile exceptions } catch ( UploadStashNotAvailableException $e ) { $this->dieUsage( "Session not available: " . $e->getMessage(), "nosession" ); } catch ( UploadStashFileNotFoundException $e ) { $this->dieUsage( "File not found: " . $e->getMessage(), "invalidsessiondata" ); } catch ( UploadStashBadPathException $e ) { $this->dieUsage( "Bad path: " . $e->getMessage(), "invalidsessiondata" ); - } - - } - - /** - * Returns all valid parameters to siiprop - */ - public static function getPropertyNames() { - return array( - 'timestamp', - 'url', - 'size', - 'dimensions', // For backwards compatibility with Allimages - 'sha1', - 'mime', - 'thumbmime', - 'metadata', - 'bitdepth', - ); + } } + private $propertyFilter = array( + 'user', 'userid', 'comment', 'parsedcomment', + 'mediatype', 'archivename', + ); public function getAllowedParams() { return array( - 'sessionkey' => array( + 'filekey' => array( ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_REQUIRED => true, + ApiBase::PARAM_DFLT => null + ), + 'sessionkey' => array( + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_DEPRECATED => true, ApiBase::PARAM_DFLT => null ), 'prop' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_DFLT => 'timestamp|url', - ApiBase::PARAM_TYPE => self::getPropertyNames() + ApiBase::PARAM_TYPE => self::getPropertyNames( $this->propertyFilter ) ), 'urlwidth' => array( ApiBase::PARAM_TYPE => 'integer', @@ -98,32 +93,28 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo { 'urlheight' => array( ApiBase::PARAM_TYPE => 'integer', ApiBase::PARAM_DFLT => -1 - ) + ), + 'urlparam' => array( + ApiBase::PARAM_TYPE => 'string', + ApiBase::PARAM_DFLT => '', + ), ); } /** * Return the API documentation for the parameters. - * @return {Array} parameter documentation. + * @return Array parameter documentation. */ public function getParamDescription() { $p = $this->getModulePrefix(); return array( - 'prop' => array( - 'What image information to get:', - ' timestamp - Adds timestamp for the uploaded version', - ' url - Gives URL to the image and the description page', - ' size - Adds the size of the image in bytes and the height and width', - ' dimensions - Alias for size', - ' sha1 - Adds sha1 hash for the image', - ' mime - Adds MIME of the image', - ' thumbmime - Adss MIME of the image thumbnail (requires url)', - ' metadata - Lists EXIF metadata for the version of the image', - ' bitdepth - Adds the bit depth of the version', - ), - 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.', + 'prop' => self::getPropertyDescriptions( $this->propertyFilter ), + 'filekey' => 'Key that identifies a previous upload that was stashed temporarily.', + 'sessionkey' => 'Alias for filekey, for backward compatibility.', 'urlwidth' => "If {$p}prop=url is set, a URL to an image scaled to this width will be returned.", - 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth" + 'urlheight' => "Similar to {$p}urlwidth. Cannot be used without {$p}urlwidth", + 'urlparam' => array( "A handler specific parameter string. For example, pdf's ", + "might use 'page15-100px'. {$p}urlwidth must be used and be consistent with {$p}urlparam" ), ); } @@ -131,21 +122,15 @@ class ApiQueryStashImageInfo extends ApiQueryImageInfo { return 'Returns image information for stashed images'; } - public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'siiurlwidth', 'info' => 'siiurlheight cannot be used without iiurlwidth' ), - ) ); - } - protected function getExamples() { return array( - 'api.php?action=query&prop=stashimageinfo&siisessionkey=124sd34rsdf567', - 'api.php?action=query&prop=stashimageinfo&siisessionkey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url', + 'api.php?action=query&prop=stashimageinfo&siifilekey=124sd34rsdf567', + 'api.php?action=query&prop=stashimageinfo&siifilekey=b34edoe3|bceffd4&siiurlwidth=120&siiprop=url', ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryStashImageInfo.php 81000 2011-01-25 22:49:34Z catrope $'; + return __CLASS__ . ': $Id: ApiQueryStashImageInfo.php 92459 2011-07-18 19:31:38Z raindrift $'; } } diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php index e88ec9b5..14ca14b7 100644 --- a/includes/api/ApiQueryTags.php +++ b/includes/api/ApiQueryTags.php @@ -1,6 +1,6 @@ addTables( 'change_tag' ); $this->addFields( 'ct_tag' ); - if ( $this->fld_hitcount ) { - $this->addFields( 'count(*) AS hitcount' ); - } + $this->addFieldsIf( 'count(*) AS hitcount', $this->fld_hitcount ); $this->addOption( 'LIMIT', $this->limit + 1 ); $this->addOption( 'GROUP BY', 'ct_tag' ); @@ -110,9 +113,8 @@ class ApiQueryTags extends ApiQueryBase { } if ( $this->fld_description ) { - $msg = wfMsg( "tag-$tagName-description" ); - $msg = wfEmptyMsg( "tag-$tagName-description", $msg ) ? '' : $msg; - $tag['description'] = $msg; + $msg = wfMessage( "tag-$tagName-description" ); + $tag['description'] = $msg->exists() ? $msg->text() : ''; } if ( $this->fld_hitcount ) { @@ -183,6 +185,6 @@ class ApiQueryTags extends ApiQueryBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryTags.php 73858 2010-09-28 01:21:15Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryTags.php 90542 2011-06-21 20:05:00Z ialex $'; } } diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 5d63fa60..e958c729 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -1,6 +1,6 @@ prefixMode = false; $this->multiUserMode = ( count( $this->params['user'] ) > 1 ); } + $this->prepareQuery(); // Do the actual query. @@ -120,6 +121,8 @@ class ApiQueryContributions extends ApiQueryBase { /** * Validate the 'user' parameter and set the value to compare * against `revision`.`rev_user_text` + * + * @param $user string */ private function prepareUsername( $user ) { if ( !is_null( $user ) && $user !== '' ) { @@ -179,7 +182,7 @@ class ApiQueryContributions extends ApiQueryBase { if ( $this->multiUserMode ) { $this->addWhereRange( 'rev_user_text', $this->params['dir'], null, null ); } - $this->addWhereRange( 'rev_timestamp', + $this->addTimestampWhereRange( 'rev_timestamp', $this->params['dir'], $this->params['start'], $this->params['end'] ); $this->addWhereFld( 'page_namespace', $this->params['namespace'] ); @@ -188,7 +191,7 @@ class ApiQueryContributions extends ApiQueryBase { $show = array_flip( $show ); if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) ) || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) ) { - $this->dieUsageMsg( array( 'show' ) ); + $this->dieUsageMsg( 'show' ); } $this->addWhereIf( 'rev_minor_edit = 0', isset( $show['!minor'] ) ); @@ -246,8 +249,7 @@ class ApiQueryContributions extends ApiQueryBase { // $this->addFieldsIf( 'rev_text_id', $this->fld_ids ); // Should this field be exposed? $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment ); $this->addFieldsIf( 'rev_len', $this->fld_size ); - $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags ); - $this->addFieldsIf( 'rev_parent_id', $this->fld_flags ); + $this->addFieldsIf( array( 'rev_minor_edit', 'rev_parent_id' ), $this->fld_flags ); $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); if ( $this->fld_tags ) { @@ -264,6 +266,10 @@ class ApiQueryContributions extends ApiQueryBase { $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; } + if ( $this->params['toponly'] ) { + $this->addWhere( 'rev_id = page_latest' ); + } + $this->addOption( 'USE INDEX', $index ); } @@ -409,6 +415,7 @@ class ApiQueryContributions extends ApiQueryBase { ) ), 'tag' => null, + 'toponly' => false, ); } @@ -422,12 +429,12 @@ class ApiQueryContributions extends ApiQueryBase { 'continue' => 'When more results are available, use this to continue', 'user' => 'The users to retrieve contributions for', 'userprefix' => "Retrieve contibutions for all users whose names begin with this value. Overrides {$p}user", - 'dir' => 'The direction to search (older or newer)', + 'dir' => $this->getDirectionDescription( $p ), 'namespace' => 'Only list contributions in these namespaces', 'prop' => array( 'Include additional pieces of information', - ' ids - Adds the page id and revision id', - ' title - Adds the title and namespace id of the page', + ' ids - Adds the page ID and revision ID', + ' title - Adds the title and namespace ID of the page', ' timestamp - Adds the timestamp of the edit', ' comment - Adds the comment of the edit', ' parsedcomment - Adds the parsed comment of the edit', @@ -439,6 +446,7 @@ class ApiQueryContributions extends ApiQueryBase { 'show' => array( "Show only items that meet this criteria, e.g. non minor edits only: {$p}show=!minor", "NOTE: if {$p}show=patrolled or {$p}show=!patrolled is set, revisions older than $wgRCMaxAge won\'t be shown", ), 'tag' => 'Only list revisions tagged with this tag', + 'toponly' => 'Only list changes which are the latest revision', ); } @@ -462,7 +470,11 @@ class ApiQueryContributions extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Usercontribs'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserContributions.php 75096 2010-10-20 18:50:33Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryUserContributions.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index ec7b74b3..8a8ce118 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -1,6 +1,6 @@ getResult(); $vals = array(); $vals['id'] = intval( $wgUser->getId() ); @@ -83,6 +83,11 @@ class ApiQueryUserInfo extends ApiQueryBase { $result->setIndexedTagName( $vals['groups'], 'g' ); // even if empty } + if ( isset( $this->prop['implicitgroups'] ) ) { + $vals['implicitgroups'] = ApiQueryUsers::getAutoGroups( $wgUser ); + $result->setIndexedTagName( $vals['implicitgroups'], 'g' ); // even if empty + } + if ( isset( $this->prop['rights'] ) ) { // User::getRights() may return duplicate values, strip them $vals['rights'] = array_values( array_unique( $wgUser->getRights() ) ); @@ -101,12 +106,10 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['options'] = $wgUser->getOptions(); } - if ( - isset( $this->prop['preferencestoken'] ) && + if ( isset( $this->prop['preferencestoken'] ) && is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) - ) - { - $vals['preferencestoken'] = $wgUser->editToken(); + ) { + $vals['preferencestoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() ); } if ( isset( $this->prop['editcount'] ) ) { @@ -117,6 +120,10 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['ratelimits'] = $this->getRateLimits(); } + if ( isset( $this->prop['realname'] ) && !in_array( 'realname', $wgHiddenPrefs ) ) { + $vals['realname'] = $wgUser->getRealName(); + } + if ( isset( $this->prop['email'] ) ) { $vals['email'] = $wgUser->getEmail(); $auth = $wgUser->getEmailAuthenticationTimestamp(); @@ -125,6 +132,13 @@ class ApiQueryUserInfo extends ApiQueryBase { } } + if ( isset( $this->prop['registrationdate'] ) ) { + $regDate = $wgUser->getRegistration(); + if ( $regDate !== false ) { + $vals['registrationdate'] = wfTimestamp( TS_ISO_8601, $regDate ); + } + } + if ( isset( $this->prop['acceptlang'] ) ) { $langs = $wgRequest->getAcceptLang(); $acceptLang = array(); @@ -182,6 +196,7 @@ class ApiQueryUserInfo extends ApiQueryBase { 'blockinfo', 'hasmsg', 'groups', + 'implicitgroups', 'rights', 'changeablegroups', 'options', @@ -189,7 +204,9 @@ class ApiQueryUserInfo extends ApiQueryBase { 'editcount', 'ratelimits', 'email', + 'realname', 'acceptlang', + 'registrationdate' ) ) ); @@ -202,13 +219,17 @@ class ApiQueryUserInfo extends ApiQueryBase { ' blockinfo - Tags if the current user is blocked, by whom, and for what reason', ' hasmsg - Adds a tag "message" if the current user has pending messages', ' groups - Lists all the groups the current user belongs to', + ' implicitgroups - Lists all the groups the current user is automatically a member of', ' rights - Lists all the rights the current user has', ' changeablegroups - Lists the groups the current user can add to and remove from', ' options - Lists all preferences the current user has set', + ' preferencestoken - Get a token to change current user\'s preferences', ' editcount - Adds the current user\'s edit count', ' ratelimits - Lists all rate limits applying to the current user', + ' realname - Adds the user\'s real name', ' email - Adds the user\'s email address and email authentication date', ' acceptlang - Echoes the Accept-Language header sent by the client in a structured format', + ' registrationdate - Adds the user\'s registration date', ) ); } @@ -224,7 +245,11 @@ class ApiQueryUserInfo extends ApiQueryBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Meta#userinfo_.2F_ui'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUserInfo.php 75937 2010-11-03 17:01:21Z reedy $'; + return __CLASS__ . ': $Id: ApiQueryUserInfo.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index 2619d200..6eee1331 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +34,7 @@ if ( !defined( 'MEDIAWIKI' ) ) { * * @ingroup API */ - class ApiQueryUsers extends ApiQueryBase { +class ApiQueryUsers extends ApiQueryBase { private $tokenFunctions, $prop; @@ -66,6 +66,10 @@ if ( !defined( 'MEDIAWIKI' ) ) { return $this->tokenFunctions; } + /** + * @param $user User + * @return String + */ public static function getUserrightsToken( $user ) { global $wgUser; // Since the permissions check for userrights is non-trivial, @@ -104,14 +108,16 @@ if ( !defined( 'MEDIAWIKI' ) ) { } } + $result = $this->getResult(); + if ( count( $goodNames ) ) { - $this->addTables( 'user', 'u1' ); - $this->addFields( 'u1.*' ); - $this->addWhereFld( 'u1.user_name', $goodNames ); + $this->addTables( 'user' ); + $this->addFields( '*' ); + $this->addWhereFld( 'user_name', $goodNames ); - if ( isset( $this->prop['groups'] ) ) { + if ( isset( $this->prop['groups'] ) || isset( $this->prop['rights'] ) ) { $this->addTables( 'user_groups' ); - $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=u1.user_id' ) ) ); + $this->addJoinConds( array( 'user_groups' => array( 'LEFT JOIN', 'ug_user=user_id' ) ) ); $this->addFields( 'ug_group' ); } @@ -119,9 +125,12 @@ if ( !defined( 'MEDIAWIKI' ) ) { $data = array(); $res = $this->select( __METHOD__ ); + foreach ( $res as $row ) { $user = User::newFromRow( $row ); $name = $user->getName(); + + $data[$name]['userid'] = $user->getId(); $data[$name]['name'] = $name; if ( isset( $this->prop['editcount'] ) ) { @@ -132,19 +141,30 @@ if ( !defined( 'MEDIAWIKI' ) ) { $data[$name]['registration'] = wfTimestampOrNull( TS_ISO_8601, $user->getRegistration() ); } - if ( isset( $this->prop['groups'] ) && !is_null( $row->ug_group ) ) { - // This row contains only one group, others will be added from other rows - $data[$name]['groups'][] = $row->ug_group; + if ( isset( $this->prop['groups'] ) ) { + if ( !isset( $data[$name]['groups'] ) ) { + $data[$name]['groups'] = self::getAutoGroups( $user ); + } + + if ( !is_null( $row->ug_group ) ) { + // This row contains only one group, others will be added from other rows + $data[$name]['groups'][] = $row->ug_group; + } + } + + if ( isset( $this->prop['implicitgroups'] ) && !isset( $data[$name]['implicitgroups'] ) ) { + $data[$name]['implicitgroups'] = self::getAutoGroups( $user ); } - if ( isset( $this->prop['rights'] ) && !is_null( $row->ug_group ) ) { + if ( isset( $this->prop['rights'] ) ) { if ( !isset( $data[$name]['rights'] ) ) { - $data[$name]['rights'] = User::getGroupPermissions( User::getImplicitGroups() ); + $data[$name]['rights'] = User::getGroupPermissions( $user->getAutomaticGroups() ); } - $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'], - User::getGroupPermissions( array( $row->ug_group ) ) ) ); - $result->setIndexedTagName( $data[$name]['rights'], 'r' ); + if ( !is_null( $row->ug_group ) ) { + $data[$name]['rights'] = array_unique( array_merge( $data[$name]['rights'], + User::getGroupPermissions( array( $row->ug_group ) ) ) ); + } } if ( $row->ipb_deleted ) { $data[$name]['hidden'] = ''; @@ -180,6 +200,7 @@ if ( !defined( 'MEDIAWIKI' ) ) { } } } + // Second pass: add result data to $retval foreach ( $goodNames as $u ) { if ( !isset( $data[$u] ) ) { @@ -207,13 +228,16 @@ if ( !defined( 'MEDIAWIKI' ) ) { } } else { if ( isset( $this->prop['groups'] ) && isset( $data[$u]['groups'] ) ) { - $autolist = ApiQueryUsers::getAutoGroups( User::newFromName( $u ) ); - - $data[$u]['groups'] = array_merge( $autolist, $data[$u]['groups'] ); - - $this->getResult()->setIndexedTagName( $data[$u]['groups'], 'g' ); + $result->setIndexedTagName( $data[$u]['groups'], 'g' ); + } + if ( isset( $this->prop['implicitgroups'] ) && isset( $data[$u]['implicitgroups'] ) ) { + $result->setIndexedTagName( $data[$u]['implicitgroups'], 'g' ); + } + if ( isset( $this->prop['rights'] ) && isset( $data[$u]['rights'] ) ) { + $result->setIndexedTagName( $data[$u]['rights'], 'r' ); } } + $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $data[$u] ); if ( !$fit ) { @@ -223,15 +247,17 @@ if ( !defined( 'MEDIAWIKI' ) ) { } $done[] = $u; } - return $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' ); + return $result->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), 'user' ); } /** - * Gets all the groups that a user is automatically a member of + * Gets all the groups that a user is automatically a member of (implicit groups) + * @param $user User * @return array */ public static function getAutoGroups( $user ) { - $groups = array( '*' ); + $groups = array(); + $groups[] = '*'; if ( !$user->isAnon() ) { $groups[] = 'user'; @@ -256,6 +282,8 @@ if ( !defined( 'MEDIAWIKI' ) ) { ApiBase::PARAM_TYPE => array( 'blockinfo', 'groups', + 'implicitgroups', + 'rights', 'editcount', 'registration', 'emailable', @@ -276,13 +304,14 @@ if ( !defined( 'MEDIAWIKI' ) ) { return array( 'prop' => array( 'What pieces of information to include', - ' blockinfo - Tags if the user is blocked, by whom, and for what reason', - ' groups - Lists all the groups the user(s) belongs to', - ' rights - Lists all the rights the user(s) has', - ' editcount - Adds the user\'s edit count', - ' registration - Adds the user\'s registration timestamp', - ' emailable - Tags if the user can and wants to receive e-mail through [[Special:Emailuser]]', - ' gender - Tags the gender of the user. Returns "male", "female", or "unknown"', + ' blockinfo - Tags if the user is blocked, by whom, and for what reason', + ' groups - Lists all the groups the user(s) belongs to', + ' implicitgroups - Lists all the groups a user is automatically a member of', + ' rights - Lists all the rights the user(s) has', + ' editcount - Adds the user\'s edit count', + ' registration - Adds the user\'s registration timestamp', + ' emailable - Tags if the user can and wants to receive e-mail through [[Special:Emailuser]]', + ' gender - Tags the gender of the user. Returns "male", "female", or "unknown"', ), 'users' => 'A list of users to obtain the same information for', 'token' => 'Which tokens to obtain for each user', @@ -297,7 +326,11 @@ if ( !defined( 'MEDIAWIKI' ) ) { return 'api.php?action=query&list=users&ususers=brion|TimStarling&usprop=groups|editcount|gender'; } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Users'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryUsers.php 85354 2011-04-04 18:25:31Z demon $'; + return __CLASS__ . ': $Id: ApiQueryUsers.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 784f89c0..a5eb23eb 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -1,6 +1,6 @@ selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' ); @@ -74,6 +78,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->fld_sizes = isset( $prop['sizes'] ); $this->fld_patrol = isset( $prop['patrol'] ); $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] ); + $this->fld_loginfo = isset( $prop['loginfo'] ); if ( $this->fld_patrol ) { if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) { @@ -85,25 +90,25 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addFields( array( 'rc_namespace', 'rc_title', - 'rc_timestamp' + 'rc_timestamp', + 'rc_type', ) ); if ( is_null( $resultPageSet ) ) { $this->addFields( array( 'rc_cur_id', - 'rc_this_oldid' + 'rc_this_oldid', + 'rc_last_oldid', ) ); - $this->addFieldsIf( 'rc_new', $this->fld_flags ); - $this->addFieldsIf( 'rc_minor', $this->fld_flags ); - $this->addFieldsIf( 'rc_bot', $this->fld_flags ); + $this->addFieldsIf( array( 'rc_new', 'rc_minor', 'rc_bot' ), $this->fld_flags ); $this->addFieldsIf( 'rc_user', $this->fld_user || $this->fld_userid ); $this->addFieldsIf( 'rc_user_text', $this->fld_user ); $this->addFieldsIf( 'rc_comment', $this->fld_comment || $this->fld_parsedcomment ); $this->addFieldsIf( 'rc_patrolled', $this->fld_patrol ); - $this->addFieldsIf( 'rc_old_len', $this->fld_sizes ); - $this->addFieldsIf( 'rc_new_len', $this->fld_sizes ); + $this->addFieldsIf( array( 'rc_old_len', 'rc_new_len' ), $this->fld_sizes ); $this->addFieldsIf( 'wl_notificationtimestamp', $this->fld_notificationtimestamp ); + $this->addFieldsIf( array( 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ), $this->fld_loginfo ); } elseif ( $params['allrev'] ) { $this->addFields( 'rc_this_oldid' ); } else { @@ -111,27 +116,33 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } $this->addTables( array( + 'recentchanges', 'watchlist', - 'page', - 'recentchanges' ) ); $userId = $user->getId(); + $this->addJoinConds( array( 'watchlist' => array('INNER JOIN', + array( + 'wl_user' => $userId, + 'wl_namespace=rc_namespace', + 'wl_title=rc_title' + ) ) ) ); + $this->addWhere( array( - 'wl_namespace = rc_namespace', - 'wl_title = rc_title', - 'rc_cur_id = page_id', - 'wl_user' => $userId, 'rc_deleted' => 0, ) ); - + $db = $this->getDB(); - $this->addWhereRange( 'rc_timestamp', $params['dir'], - $db->timestamp( $params['start'] ), - $db->timestamp( $params['end'] ) ); + $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], + $params['start'], $params['end'] ); $this->addWhereFld( 'wl_namespace', $params['namespace'] ); - $this->addWhereIf( 'rc_this_oldid=page_latest', !$params['allrev'] ); + + if ( !$params['allrev'] ) { + $this->addTables( 'page' ); + $this->addJoinConds( array( 'page' => array( 'LEFT JOIN','rc_cur_id=page_id' ) ) ); + $this->addWhere( 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG ); + } if ( !is_null( $params['show'] ) ) { $show = array_flip( $params['show'] ); @@ -143,7 +154,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) ) ) { - $this->dieUsageMsg( array( 'show' ) ); + $this->dieUsageMsg( 'show' ); } // Check permissions. @@ -172,11 +183,9 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addWhereFld( 'rc_user_text', $params['user'] ); } if ( !is_null( $params['excludeuser'] ) ) { - $this->addWhere( 'rc_user_text != ' . $this->getDB()->addQuotes( $params['excludeuser'] ) ); + $this->addWhere( 'rc_user_text != ' . $db->addQuotes( $params['excludeuser'] ) ); } - - // This is an index optimization for mysql, as done in the Special:Watchlist page $this->addWhereIf( "rc_timestamp > ''", !isset( $params['start'] ) && !isset( $params['end'] ) && $db->getType() == 'mysql' ); @@ -225,6 +234,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if ( $this->fld_ids ) { $vals['pageid'] = intval( $row->rc_cur_id ); $vals['revid'] = intval( $row->rc_this_oldid ); + $vals['old_revid'] = intval( $row->rc_last_oldid ); } $title = Title::makeTitle( $row->rc_namespace, $row->rc_title ); @@ -240,7 +250,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } if ( $this->fld_userid ) { - $vals['user'] = $row->rc_user; + $vals['user'] = $row->rc_user; } if ( !$row->rc_user ) { @@ -284,8 +294,21 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } if ( $this->fld_parsedcomment && isset( $row->rc_comment ) ) { - global $wgUser; - $vals['parsedcomment'] = $wgUser->getSkin()->formatComment( $row->rc_comment, $title ); + $vals['parsedcomment'] = Linker::formatComment( $row->rc_comment, $title ); + } + + if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) { + $vals['logid'] = intval( $row->rc_logid ); + $vals['logtype'] = $row->rc_log_type; + $vals['logaction'] = $row->rc_log_action; + ApiQueryLogEvents::addLogParams( + $this->getResult(), + $vals, + $row->rc_params, + $row->rc_log_type, + $row->rc_log_action, + $row->rc_timestamp + ); } return $vals; @@ -338,7 +361,8 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'timestamp', 'patrol', 'sizes', - 'notificationtimestamp' + 'notificationtimestamp', + 'loginfo', ) ), 'show' => array( @@ -364,6 +388,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { } public function getParamDescription() { + $p = $this->getModulePrefix(); return array( 'allrev' => 'Include multiple revisions of the same page within given timeframe', 'start' => 'The timestamp to start enumerating from', @@ -371,7 +396,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'namespace' => 'Filter changes to only the given namespace(s)', 'user' => 'Only list changes by this user', 'excludeuser' => 'Don\'t list changes by this user', - 'dir' => 'In which direction to enumerate pages', + 'dir' => $this->getDirectionDescription( $p ), 'limit' => 'How many total results to return per request', 'prop' => array( 'Which additional items to get (non-generator mode only).', @@ -384,12 +409,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { ' parsedcomment - Adds parsed comment of the edit', ' timestamp - Adds timestamp of the edit', ' patrol - Tags edits that are patrolled', - ' size - Adds the old and new lengths of the page', + ' sizes - Adds the old and new lengths of the page', ' notificationtimestamp - Adds timestamp of when the user was last notified about the edit', + ' loginfo - Adds log information where appropriate', ), 'show' => array( 'Show only items that meet this criteria.', - "For example, to see only minor edits done by logged-in users, set {$this->getModulePrefix()}show=minor|!anon" + "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon" ), 'owner' => 'The name of the user whose watchlist you\'d like to access', 'token' => 'Give a security token (settable in preferences) to allow access to another user\'s watchlist' @@ -423,7 +449,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Watchlist'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlist.php 85435 2011-04-05 14:00:08Z demon $'; + return __CLASS__ . ': $Id: ApiQueryWatchlist.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php index 0e5617e3..b008eab2 100644 --- a/includes/api/ApiQueryWatchlistRaw.php +++ b/includes/api/ApiQueryWatchlistRaw.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2008 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,6 +49,10 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { $this->run( $resultPageSet ); } + /** + * @param $resultPageSet ApiPageSet + * @return void + */ private function run( $resultPageSet = null ) { $this->selectNamedDB( 'watchlist', DB_SLAVE, 'watchlist' ); @@ -59,7 +63,7 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { $prop = array_flip( (array)$params['prop'] ); $show = array_flip( (array)$params['show'] ); if ( isset( $show['changed'] ) && isset( $show['!changed'] ) ) { - $this->dieUsageMsg( array( 'show' ) ); + $this->dieUsageMsg( 'show' ); } $this->addTables( 'watchlist' ); @@ -201,6 +205,6 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 70647 2010-08-07 19:59:42Z ialex $'; + return __CLASS__ . ': $Id: ApiQueryWatchlistRaw.php 88416 2011-05-19 17:51:16Z hashar $'; } -} \ No newline at end of file +} diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index 9d42a58e..f7ea0045 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -1,6 +1,6 @@ mCheckingSize ) { $newsize = $this->mSize + self::size( $value ); if ( $newsize > $wgAPIMaxResultSize ) { + $this->setWarning( + "This result was truncated because it would otherwise be larger than the " . + "limit of {$wgAPIMaxResultSize} bytes" ); return false; } $this->mSize = $newsize; @@ -327,6 +336,8 @@ class ApiResult extends ApiBase { /** * Callback function for cleanUpUTF8() + * + * @param $s string */ private static function cleanUp_helper( &$s ) { if ( !is_string( $s ) ) { @@ -336,11 +347,31 @@ class ApiResult extends ApiBase { $s = $wgContLang->normalize( $s ); } + /** + * Converts a Status object to an array suitable for addValue + * @param Status $status + * @param string $errorType + * @return array + */ + public function convertStatusToArray( $status, $errorType = 'error' ) { + if ( $status->isGood() ) { + return array(); + } + + $result = array(); + foreach ( $status->getErrorsByType( $errorType ) as $error ) { + $this->setIndexedTagName( $error['params'], 'param' ); + $result[] = $error; + } + $this->setIndexedTagName( $result, $errorType ); + return $result; + } + public function execute() { ApiBase::dieDebug( __METHOD__, 'execute() is not supported on Result object' ); } public function getVersion() { - return __CLASS__ . ': $Id: ApiResult.php 74230 2010-10-03 19:07:11Z reedy $'; + return __CLASS__ . ': $Id: ApiResult.php 91144 2011-06-29 23:46:39Z reedy $'; } } diff --git a/includes/api/ApiRollback.php b/includes/api/ApiRollback.php index e31bfed8..a149fcaf 100644 --- a/includes/api/ApiRollback.php +++ b/includes/api/ApiRollback.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,7 +38,15 @@ class ApiRollback extends ApiBase { parent::__construct( $main, $action ); } - private $mTitleObj = null, $mUser = null; + /** + * @var Title + */ + private $mTitleObj = null; + + /** + * @var User + */ + private $mUser = null; public function execute() { $params = $this->extractRequestParams(); @@ -47,7 +55,7 @@ class ApiRollback extends ApiBase { $titleObj = $this->getTitle(); $articleObj = new Article( $titleObj ); $summary = ( isset( $params['summary'] ) ? $params['summary'] : '' ); - $details = null; + $details = array(); $retval = $articleObj->doRollback( $this->getUser(), $summary, $params['token'], $params['markbot'], $details ); if ( $retval ) { @@ -170,7 +178,7 @@ class ApiRollback extends ApiBase { $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); } if ( !$this->mTitleObj->exists() ) { - $this->dieUsageMsg( array( 'notanarticle' ) ); + $this->dieUsageMsg( 'notanarticle' ); } return $this->mTitleObj; @@ -183,7 +191,11 @@ class ApiRollback extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Rollback'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiRollback.php 75602 2010-10-28 00:04:48Z reedy $'; + return __CLASS__ . ': $Id: ApiRollback.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php index 7bc4722c..f9a4d285 100644 --- a/includes/api/ApiRsd.php +++ b/includes/api/ApiRsd.php @@ -47,7 +47,8 @@ class ApiRsd extends ApiBase { $service = array( 'apis' => $this->formatRsdApiList() ); ApiResult::setContent( $service, 'MediaWiki', 'engineName' ); - ApiResult::setContent( $service, 'http://www.mediawiki.org/', 'engineLink' ); + ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' ); + ApiResult::setContent( $service, Title::newMainPage()->getCanonicalUrl(), 'homePageLink' ); $result->setIndexedTagName( $service['apis'], 'api' ); @@ -67,7 +68,7 @@ class ApiRsd extends ApiBase { } public function getDescription() { - return 'Export an RSD schema'; + return 'Export an RSD (Really Simple Discovery) schema'; } protected function getExamples() { @@ -97,10 +98,10 @@ class ApiRsd extends ApiBase { $apis = array( 'MediaWiki' => array( // The API link is required for all RSD API entries. - 'apiLink' => wfExpandUrl( wfScript( 'api' ) ), + 'apiLink' => wfExpandUrl( wfScript( 'api' ), PROTO_CURRENT ), // Docs link is optional, but recommended. - 'docs' => 'http://mediawiki.org/wiki/API', + 'docs' => 'https://www.mediawiki.org/wiki/API', // Some APIs may need a blog ID, but it may be left blank. 'blogID' => '', @@ -160,7 +161,7 @@ class ApiRsd extends ApiBase { } public function getVersion() { - return __CLASS__ . ': $Id: ApiRsd.php 76195 2010-11-06 15:57:15Z btongminh $'; + return __CLASS__ . ': $Id: ApiRsd.php 104449 2011-11-28 15:52:04Z reedy $'; } } @@ -169,12 +170,12 @@ class ApiFormatXmlRsd extends ApiFormatXml { parent::__construct( $main, $format ); $this->setRootElement( 'rsd' ); } - + public function getMimeType() { return 'application/rsd+xml'; } public function getVersion() { - return __CLASS__ . ': $Id: ApiRsd.php 76195 2010-11-06 15:57:15Z btongminh $'; + return __CLASS__ . ': $Id: ApiRsd.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiUnblock.php b/includes/api/ApiUnblock.php index 4f6e4fb7..51ee0241 100644 --- a/includes/api/ApiUnblock.php +++ b/includes/api/ApiUnblock.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -49,40 +49,42 @@ class ApiUnblock extends ApiBase { $params = $this->extractRequestParams(); if ( $params['gettoken'] ) { - $res['unblocktoken'] = $wgUser->editToken(); + $res['unblocktoken'] = $wgUser->editToken( '', $this->getMain()->getRequest() ); $this->getResult()->addValue( null, $this->getModuleName(), $res ); return; } if ( is_null( $params['id'] ) && is_null( $params['user'] ) ) { - $this->dieUsageMsg( array( 'unblock-notarget' ) ); + $this->dieUsageMsg( 'unblock-notarget' ); } if ( !is_null( $params['id'] ) && !is_null( $params['user'] ) ) { - $this->dieUsageMsg( array( 'unblock-idanduser' ) ); + $this->dieUsageMsg( 'unblock-idanduser' ); } if ( !$wgUser->isAllowed( 'block' ) ) { - $this->dieUsageMsg( array( 'cantunblock' ) ); + $this->dieUsageMsg( 'cantunblock' ); } # bug 15810: blocked admins should have limited access here if ( $wgUser->isBlocked() ) { - $status = IPBlockForm::checkUnblockSelf( $params['user'] ); + $status = SpecialBlock::checkUnblockSelf( $params['user'] ); if ( $status !== true ) { - $this->dieUsageMsg( array( $status ) ); + $this->dieUsageMsg( $status ); } } - $id = $params['id']; - $user = $params['user']; - $reason = ( is_null( $params['reason'] ) ? '' : $params['reason'] ); - $retval = IPUnblockForm::doUnblock( $id, $user, $reason, $range ); - if ( $retval ) { - $this->dieUsageMsg( $retval ); + $data = array( + 'Target' => is_null( $params['id'] ) ? $params['user'] : "#{$params['id']}", + 'Reason' => is_null( $params['reason'] ) ? '' : $params['reason'] + ); + $block = Block::newFromTarget( $data['Target'] ); + $retval = SpecialUnblock::processUnblock( $data ); + if ( $retval !== true ) { + $this->dieUsageMsg( $retval[0] ); } - $res['id'] = intval( $id ); - $res['user'] = $user; - $res['reason'] = $reason; + $res['id'] = $block->getId(); + $res['user'] = $block->getType() == Block::TYPE_AUTO ? '' : $block->getTarget(); + $res['reason'] = $params['reason']; $this->getResult()->addValue( null, $this->getModuleName(), $res ); } @@ -96,7 +98,9 @@ class ApiUnblock extends ApiBase { public function getAllowedParams() { return array( - 'id' => null, + 'id' => array( + ApiBase::PARAM_TYPE => 'integer', + ), 'user' => null, 'token' => null, 'gettoken' => false, @@ -144,7 +148,11 @@ class ApiUnblock extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Block'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiUnblock.php 74098 2010-10-01 20:12:50Z reedy $'; + return __CLASS__ . ': $Id: ApiUnblock.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiUndelete.php b/includes/api/ApiUndelete.php index 3c7d91a5..c2aa2a00 100644 --- a/includes/api/ApiUndelete.php +++ b/includes/api/ApiUndelete.php @@ -1,10 +1,10 @@ .@home.nl + * Copyright © 2007 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -43,11 +43,11 @@ class ApiUndelete extends ApiBase { $params = $this->extractRequestParams(); if ( !$wgUser->isAllowed( 'undelete' ) ) { - $this->dieUsageMsg( array( 'permdenied-undelete' ) ); + $this->dieUsageMsg( 'permdenied-undelete' ); } if ( $wgUser->isBlocked() ) { - $this->dieUsageMsg( array( 'blockedtext' ) ); + $this->dieUsageMsg( 'blockedtext' ); } $titleObj = Title::newFromText( $params['title'] ); @@ -69,7 +69,7 @@ class ApiUndelete extends ApiBase { $pa = new PageArchive( $titleObj ); $retval = $pa->undelete( ( isset( $params['timestamps'] ) ? $params['timestamps'] : array() ), $params['reason'] ); if ( !is_array( $retval ) ) { - $this->dieUsageMsg( array( 'cannotundelete' ) ); + $this->dieUsageMsg( 'cannotundelete' ); } if ( $retval[1] ) { @@ -103,7 +103,8 @@ class ApiUndelete extends ApiBase { 'token' => null, 'reason' => '', 'timestamps' => array( - ApiBase::PARAM_ISMULTI => true + ApiBase::PARAM_TYPE => 'timestamp', + ApiBase::PARAM_ISMULTI => true, ), 'watchlist' => array( ApiBase::PARAM_DFLT => 'preferences', @@ -158,7 +159,11 @@ class ApiUndelete extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Undelete'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiUndelete.php 74098 2010-10-01 20:12:50Z reedy $'; + return __CLASS__ . ': $Id: ApiUndelete.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php index e7d7b939..1dab0310 100644 --- a/includes/api/ApiUpload.php +++ b/includes/api/ApiUpload.php @@ -1,6 +1,6 @@ dieUsageMsg( array( 'uploaddisabled' ) ); + $this->dieUsageMsg( 'uploaddisabled' ); } // Parameter handling @@ -54,6 +59,11 @@ class ApiUpload extends ApiBase { // Add the uploaded file to the params array $this->mParams['file'] = $request->getFileName( 'file' ); + // Copy the session key to the file key, for backward compatibility. + if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) { + $this->mParams['filekey'] = $this->mParams['sessionkey']; + } + // Select an upload module if ( !$this->selectUploadModule() ) { // This is not a true upload, but a status request or similar @@ -77,34 +87,40 @@ class ApiUpload extends ApiBase { // Check if the uploaded file is sane $this->verifyUpload(); - // Check permission to upload this file - $permErrors = $this->mUpload->verifyPermissions( $wgUser ); - if ( $permErrors !== true ) { - // TODO: stash the upload and allow choosing a new name - $this->dieUsageMsg( array( 'badaccess-groups' ) ); + + // Check if the user has the rights to modify or overwrite the requested title + // (This check is irrelevant if stashing is already requested, since the errors + // can always be fixed by changing the title) + if ( ! $this->mParams['stash'] ) { + $permErrors = $this->mUpload->verifyTitlePermissions( $wgUser ); + if ( $permErrors !== true ) { + $this->dieRecoverableError( $permErrors[0], 'filename' ); + } } // Prepare the API result $result = array(); - + $warnings = $this->getApiWarnings(); - if ( $warnings ) { + if ( $warnings ) { $result['result'] = 'Warning'; $result['warnings'] = $warnings; // in case the warnings can be fixed with some further user action, let's stash this upload // and return a key they can use to restart it - try { - $result['sessionkey'] = $this->performStash(); - } catch ( MWException $e ) { + try { + $result['filekey'] = $this->performStash(); + $result['sessionkey'] = $result['filekey']; // backwards compatibility + } catch ( MWException $e ) { $result['warnings']['stashfailed'] = $e->getMessage(); } - } elseif ( $this->mParams['stash'] ) { + } elseif ( $this->mParams['stash'] ) { // Some uploads can request they be stashed, so as not to publish them immediately. // In this case, a failure to stash ought to be fatal try { - $result['result'] = 'Success'; - $result['sessionkey'] = $this->performStash(); - } catch ( MWException $e ) { + $result['result'] = 'Success'; + $result['filekey'] = $this->performStash(); + $result['sessionkey'] = $result['filekey']; // backwards compatibility + } catch ( MWException $e ) { $this->dieUsage( $e->getMessage(), 'stashfailed' ); } } else { @@ -113,52 +129,76 @@ class ApiUpload extends ApiBase { $result = $this->performUpload(); } - if ( $result['result'] === 'Success' ) { + if ( $result['result'] === 'Success' ) { $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() ); } $this->getResult()->addValue( null, $this->getModuleName(), $result ); - + // Cleanup any temporary mess $this->mUpload->cleanupTempFile(); } /** - * Stash the file and return the session key + * Stash the file and return the file key * Also re-raises exceptions with slightly more informative message strings (useful for API) * @throws MWException - * @return {String} session key + * @return String file key */ function performStash() { try { - $sessionKey = $this->mUpload->stashSessionFile()->getSessionKey(); + $fileKey = $this->mUpload->stashFile()->getFileKey(); } catch ( MWException $e ) { - throw new MWException( 'Stashing temporary file failed: ' . get_class($e) . ' ' . $e->getMessage() ); + $message = 'Stashing temporary file failed: ' . get_class( $e ) . ' ' . $e->getMessage(); + wfDebug( __METHOD__ . ' ' . $message . "\n"); + throw new MWException( $message ); } - return $sessionKey; + return $fileKey; } + /** + * Throw an error that the user can recover from by providing a better + * value for $parameter + * + * @param $error array Error array suitable for passing to dieUsageMsg() + * @param $parameter string Parameter that needs revising + * @param $data array Optional extra data to pass to the user + * @throws UsageException + */ + function dieRecoverableError( $error, $parameter, $data = array() ) { + try { + $data['filekey'] = $this->performStash(); + $data['sessionkey'] = $data['filekey']; + } catch ( MWException $e ) { + $data['stashfailed'] = $e->getMessage(); + } + $data['invalidparameter'] = $parameter; + + $parsed = $this->parseMsg( $error ); + $this->dieUsage( $parsed['info'], $parsed['code'], 0, $data ); + } /** * Select an upload module and set it to mUpload. Dies on failure. If the - * request was a status request and not a true upload, returns false; + * request was a status request and not a true upload, returns false; * otherwise true - * + * * @return bool */ protected function selectUploadModule() { - global $wgAllowAsyncCopyUploads; $request = $this->getMain()->getRequest(); // One and only one of the following parameters is needed $this->requireOnlyOneParameter( $this->mParams, - 'sessionkey', 'file', 'url', 'statuskey' ); + 'filekey', 'file', 'url', 'statuskey' ); + + if ( $this->mParams['statuskey'] ) { + $this->checkAsyncDownloadEnabled(); - if ( $wgAllowAsyncCopyUploads && $this->mParams['statuskey'] ) { // Status request for an async upload $sessionData = UploadFromUrlJob::getSessionData( $this->mParams['statuskey'] ); if ( !isset( $sessionData['result'] ) ) { - $this->dieUsage( 'No result in session data', 'missingresult'); + $this->dieUsage( 'No result in session data', 'missingresult' ); } if ( $sessionData['result'] == 'Warning' ) { $sessionData['warnings'] = $this->transformWarnings( $sessionData['warnings'] ); @@ -166,28 +206,24 @@ class ApiUpload extends ApiBase { } $this->getResult()->addValue( null, $this->getModuleName(), $sessionData ); return false; - - } + } // The following modules all require the filename parameter to be set if ( is_null( $this->mParams['filename'] ) ) { $this->dieUsageMsg( array( 'missingparam', 'filename' ) ); } - - if ( $this->mParams['sessionkey'] ) { + if ( $this->mParams['filekey'] ) { // Upload stashed in a previous request - $sessionData = $request->getSessionData( UploadBase::getSessionKeyName() ); - if ( !UploadFromStash::isValidSessionKey( $this->mParams['sessionkey'], $sessionData ) ) { - $this->dieUsageMsg( array( 'invalid-session-key' ) ); + if ( !UploadFromStash::isValidKey( $this->mParams['filekey'] ) ) { + $this->dieUsageMsg( 'invalid-file-key' ); } - $this->mUpload = new UploadFromStash(); - $this->mUpload->initialize( $this->mParams['filename'], - $this->mParams['sessionkey'], - $sessionData[$this->mParams['sessionkey']] ); - + // context allows access to the current user without creating new $wgUser references + $context = $this->createContext(); + $this->mUpload = new UploadFromStash( $context->getUser() ); + $this->mUpload->initialize( $this->mParams['filekey'], $this->mParams['filename'] ); } elseif ( isset( $this->mParams['file'] ) ) { $this->mUpload = new UploadFromFile(); @@ -198,16 +234,18 @@ class ApiUpload extends ApiBase { } elseif ( isset( $this->mParams['url'] ) ) { // Make sure upload by URL is enabled: if ( !UploadFromUrl::isEnabled() ) { - $this->dieUsageMsg( array( 'copyuploaddisabled' ) ); + $this->dieUsageMsg( 'copyuploaddisabled' ); } $async = false; if ( $this->mParams['asyncdownload'] ) { + $this->checkAsyncDownloadEnabled(); + if ( $this->mParams['leavemessage'] && !$this->mParams['ignorewarnings'] ) { $this->dieUsage( 'Using leavemessage without ignorewarnings is not supported', 'missing-ignorewarnings' ); } - + if ( $this->mParams['leavemessage'] ) { $async = 'async-leavemessage'; } else { @@ -219,7 +257,7 @@ class ApiUpload extends ApiBase { $this->mParams['url'], $async ); } - + return true; } @@ -236,7 +274,7 @@ class ApiUpload extends ApiBase { if ( !$user->isLoggedIn() ) { $this->dieUsageMsg( array( 'mustbeloggedin', 'upload' ) ); } else { - $this->dieUsageMsg( array( 'badaccess-groups' ) ); + $this->dieUsageMsg( 'badaccess-groups' ); } } } @@ -254,15 +292,29 @@ class ApiUpload extends ApiBase { // TODO: Move them to ApiBase's message map switch( $verification['status'] ) { + // Recoverable errors + case UploadBase::MIN_LENGTH_PARTNAME: + $this->dieRecoverableError( 'filename-tooshort', 'filename' ); + break; + case UploadBase::ILLEGAL_FILENAME: + $this->dieRecoverableError( 'illegal-filename', 'filename', + array( 'filename' => $verification['filtered'] ) ); + break; + case UploadBase::FILETYPE_MISSING: + $this->dieRecoverableError( 'filetype-missing', 'filename' ); + break; + case UploadBase::WINDOWS_NONASCII_FILENAME: + $this->dieRecoverableError( 'windows-nonascii-filename', 'filename' ); + break; + + // Unrecoverable errors case UploadBase::EMPTY_FILE: $this->dieUsage( 'The file you submitted was empty', 'empty-file' ); break; case UploadBase::FILE_TOO_LARGE: $this->dieUsage( 'The file you submitted was too large', 'file-too-large' ); break; - case UploadBase::FILETYPE_MISSING: - $this->dieUsage( 'The file is missing an extension', 'filetype-missing' ); - break; + case UploadBase::FILETYPE_BADTYPE: $this->dieUsage( 'This type of file is banned', 'filetype-banned', 0, array( @@ -270,13 +322,6 @@ class ApiUpload extends ApiBase { 'allowed' => $wgFileExtensions ) ); break; - case UploadBase::MIN_LENGTH_PARTNAME: - $this->dieUsage( 'The filename is too short', 'filename-tooshort' ); - break; - case UploadBase::ILLEGAL_FILENAME: - $this->dieUsage( 'The filename is not allowed', 'illegal-filename', - 0, array( 'filename' => $verification['filtered'] ) ); - break; case UploadBase::VERIFICATION_ERROR: $this->getResult()->setIndexedTagName( $verification['details'], 'detail' ); $this->dieUsage( 'This file did not pass file verification', 'verification-error', @@ -306,30 +351,35 @@ class ApiUpload extends ApiBase { if ( !$this->mParams['ignorewarnings'] ) { $warnings = $this->mUpload->checkWarnings(); - if ( $warnings ) { - // Add indices - $this->getResult()->setIndexedTagName( $warnings, 'warning' ); - - if ( isset( $warnings['duplicate'] ) ) { - $dupes = array(); - foreach ( $warnings['duplicate'] as $dupe ) { - $dupes[] = $dupe->getName(); - } - $this->getResult()->setIndexedTagName( $dupes, 'duplicate' ); - $warnings['duplicate'] = $dupes; - } + } + return $this->transformWarnings( $warnings ); + } + + protected function transformWarnings( $warnings ) { + if ( $warnings ) { + // Add indices + $result = $this->getResult(); + $result->setIndexedTagName( $warnings, 'warning' ); - if ( isset( $warnings['exists'] ) ) { - $warning = $warnings['exists']; - unset( $warnings['exists'] ); - $warnings[$warning['warning']] = $warning['file']->getName(); + if ( isset( $warnings['duplicate'] ) ) { + $dupes = array(); + foreach ( $warnings['duplicate'] as $dupe ) { + $dupes[] = $dupe->getName(); } + $result->setIndexedTagName( $dupes, 'duplicate' ); + $warnings['duplicate'] = $dupes; } - } + if ( isset( $warnings['exists'] ) ) { + $warning = $warnings['exists']; + unset( $warnings['exists'] ); + $warnings[$warning['warning']] = $warning['file']->getName(); + } + } return $warnings; } + /** * Perform the actual upload. Returns a suitable result array on success; * dies on failure. @@ -376,10 +426,19 @@ class ApiUpload extends ApiBase { $result['result'] = 'Success'; $result['filename'] = $file->getName(); - return $result; } + /** + * Checks if asynchronous copy uploads are enabled and throws an error if they are not. + */ + protected function checkAsyncDownloadEnabled() { + global $wgAllowAsyncCopyUploads; + if ( !$wgAllowAsyncCopyUploads ) { + $this->dieUsage( 'Asynchronous copy uploads disabled', 'asynccopyuploaddisabled'); + } + } + public function mustBePosted() { return true; } @@ -413,18 +472,18 @@ class ApiUpload extends ApiBase { 'ignorewarnings' => false, 'file' => null, 'url' => null, - 'sessionkey' => null, + 'filekey' => null, + 'sessionkey' => array( + ApiBase::PARAM_DFLT => null, + ApiBase::PARAM_DEPRECATED => true, + ), 'stash' => false, + + 'asyncdownload' => false, + 'leavemessage' => false, + 'statuskey' => null, ); - global $wgAllowAsyncCopyUploads; - if ( $wgAllowAsyncCopyUploads ) { - $params += array( - 'asyncdownload' => false, - 'leavemessage' => false, - 'statuskey' => null, - ); - } return $params; } @@ -439,18 +498,14 @@ class ApiUpload extends ApiBase { 'ignorewarnings' => 'Ignore any warnings', 'file' => 'File contents', 'url' => 'Url to fetch the file from', - 'sessionkey' => 'Session key that identifies a previous upload that was stashed temporarily.', - 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.' - ); + 'filekey' => 'Key that identifies a previous upload that was stashed temporarily.', + 'sessionkey' => 'Same as filekey, maintained for backward compatibility.', + 'stash' => 'If set, the server will not add the file to the repository and stash it temporarily.', - global $wgAllowAsyncCopyUploads; - if ( $wgAllowAsyncCopyUploads ) { - $params += array( - 'asyncdownload' => 'Make fetching a URL asynchronous', - 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished', - 'statuskey' => 'Fetch the upload status for this session key', - ); - } + 'asyncdownload' => 'Make fetching a URL asynchronous', + 'leavemessage' => 'If asyncdownload is used, leave a message on the user talk page if finished', + 'statuskey' => 'Fetch the upload status for this file key', + ); return $params; @@ -461,32 +516,32 @@ class ApiUpload extends ApiBase { 'Upload a file, or get the status of pending uploads. Several methods are available:', ' * Upload file contents directly, using the "file" parameter', ' * Have the MediaWiki server fetch a file from a URL, using the "url" parameter', - ' * Complete an earlier upload that failed due to warnings, using the "sessionkey" parameter', + ' * Complete an earlier upload that failed due to warnings, using the "filekey" parameter', 'Note that the HTTP POST must be done as a file upload (i.e. using multipart/form-data) when', - 'sending the "file". Note also that queries using session keys must be', - 'done in the same login session as the query that originally returned the key (i.e. do not', - 'log out and then log back in). Also you must get and send an edit token before doing any upload stuff' + 'sending the "file". Also you must get and send an edit token before doing any upload stuff' ); } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'uploaddisabled' ), - array( 'invalid-session-key' ), - array( 'uploaddisabled' ), - array( 'badaccess-groups' ), - array( 'mustbeloggedin', 'upload' ), - array( 'badaccess-groups' ), - array( 'badaccess-groups' ), - array( 'code' => 'fetchfileerror', 'info' => '' ), - array( 'code' => 'nomodule', 'info' => 'No upload module set' ), - array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ), - array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ), - array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ), - array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ), - array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ), - array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ), - ) ); + return array_merge( parent::getPossibleErrors(), + $this->getRequireOnlyOneParameterErrorMessages( array( 'filekey', 'file', 'url', 'statuskey' ) ), + array( + array( 'uploaddisabled' ), + array( 'invalid-file-key' ), + array( 'uploaddisabled' ), + array( 'mustbeloggedin', 'upload' ), + array( 'badaccess-groups' ), + array( 'code' => 'fetchfileerror', 'info' => '' ), + array( 'code' => 'nomodule', 'info' => 'No upload module set' ), + array( 'code' => 'empty-file', 'info' => 'The file you submitted was empty' ), + array( 'code' => 'filetype-missing', 'info' => 'The file is missing an extension' ), + array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ), + array( 'code' => 'overwrite', 'info' => 'Overwriting an existing file is not allowed' ), + array( 'code' => 'stashfailed', 'info' => 'Stashing temporary file failed' ), + array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ), + array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ), + ) + ); } public function needsToken() { @@ -502,11 +557,15 @@ class ApiUpload extends ApiBase { 'Upload from a URL:', ' api.php?action=upload&filename=Wiki.png&url=http%3A//upload.wikimedia.org/wikipedia/en/b/bc/Wiki.png', 'Complete an upload that failed due to warnings:', - ' api.php?action=upload&filename=Wiki.png&sessionkey=sessionkey&ignorewarnings=1', + ' api.php?action=upload&filename=Wiki.png&filekey=filekey&ignorewarnings=1', ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Upload'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiUpload.php 51812 2009-06-12 23:45:20Z dale $'; + return __CLASS__ . ': $Id: ApiUpload.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php index f9fe9ad2..93d4ef25 100644 --- a/includes/api/ApiUserrights.php +++ b/includes/api/ApiUserrights.php @@ -1,11 +1,11 @@ .@home.nl + * Copyright © 2009 Roan Kattouw .@gmail.com * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -53,9 +53,10 @@ class ApiUserrights extends ApiBase { $user, (array)$params['add'], (array)$params['remove'], $params['reason'] ); - $this->getResult()->setIndexedTagName( $r['added'], 'group' ); - $this->getResult()->setIndexedTagName( $r['removed'], 'group' ); - $this->getResult()->addValue( null, $this->getModuleName(), $r ); + $result = $this->getResult(); + $result->setIndexedTagName( $r['added'], 'group' ); + $result->setIndexedTagName( $r['removed'], 'group' ); + $result->addValue( null, $this->getModuleName(), $r ); } /** @@ -138,7 +139,11 @@ class ApiUserrights extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:User_group_membership'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiUserrights.php 75602 2010-10-28 00:04:48Z reedy $'; + return __CLASS__ . ': $Id: ApiUserrights.php 104449 2011-11-28 15:52:04Z reedy $'; } } diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index e9560a4d..6fc55905 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -1,6 +1,6 @@ extractRequestParams(); $title = Title::newFromText( $params['title'] ); - if ( !$title ) { + if ( !$title || $title->getNamespace() < 0 ) { $this->dieUsageMsg( array( 'invalidtitle', $params['title'] ) ); } - $article = new Article( $title ); + $article = new Article( $title, 0 ); $res = array( 'title' => $title->getPrefixedText() ); if ( $params['unwatch'] ) { $res['unwatched'] = ''; $res['message'] = wfMsgExt( 'removedwatchtext', array( 'parse' ), $title->getPrefixedText() ); - $success = $article->doUnwatch(); + $success = WatchAction::doUnwatch( $title, $wgUser ); } else { $res['watched'] = ''; $res['message'] = wfMsgExt( 'addedwatchtext', array( 'parse' ), $title->getPrefixedText() ); - $success = $article->doWatch(); + $success = UnwatchAction::doWatch( $title, $wgUser ); } if ( !$success ) { - $this->dieUsageMsg( array( 'hookaborted' ) ); + $this->dieUsageMsg( 'hookaborted' ); } $this->getResult()->addValue( null, $this->getModuleName(), $res ); } + public function mustBePosted() { + return true; + } + public function isWriteMode() { return true; } + public function needsToken() { + return true; + } + + public function getTokenSalt() { + return 'watch'; + } + public function getAllowedParams() { return array( 'title' => array( ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true ), - 'unwatch' => false, + 'token' => null, ); } @@ -90,6 +102,7 @@ class ApiWatch extends ApiBase { return array( 'title' => 'The page to (un)watch', 'unwatch' => 'If set the page will be unwatched rather than watched', + 'token' => 'A token previously acquired via prop=info', ); } @@ -112,7 +125,11 @@ class ApiWatch extends ApiBase { ); } + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Watch'; + } + public function getVersion() { - return __CLASS__ . ': $Id: ApiWatch.php 77192 2010-11-23 22:05:27Z btongminh $'; + return __CLASS__ . ': $Id: ApiWatch.php 104449 2011-11-28 15:52:04Z reedy $'; } } -- cgit v1.2.3-54-g00ecf