diff options
Diffstat (limited to 'includes/api')
70 files changed, 1349 insertions, 652 deletions
diff --git a/includes/api/ApiBase.php b/includes/api/ApiBase.php index 9351a8d8..ce6ecda6 100644 --- a/includes/api/ApiBase.php +++ b/includes/api/ApiBase.php @@ -304,15 +304,14 @@ abstract class ApiBase extends ContextSource { } $examples = $this->getExamples(); - if ( $examples !== false && $examples !== '' ) { + if ( $examples ) { if ( !is_array( $examples ) ) { $examples = array( $examples ); } $msg .= "Example" . ( count( $examples ) > 1 ? 's' : '' ) . ":\n"; - foreach( $examples as $k => $v ) { - + foreach ( $examples as $k => $v ) { if ( is_numeric( $k ) ) { $msg .= " $v\n"; } else { @@ -445,7 +444,7 @@ abstract class ApiBase extends ContextSource { $hintPipeSeparated = false; break; case 'limit': - $desc .= $paramPrefix . "No more than {$paramSettings[self :: PARAM_MAX]}"; + $desc .= $paramPrefix . "No more than {$paramSettings[self::PARAM_MAX]}"; if ( isset( $paramSettings[self::PARAM_MAX2] ) ) { $desc .= " ({$paramSettings[self::PARAM_MAX2]} for bots)"; } @@ -689,9 +688,9 @@ abstract class ApiBase extends ContextSource { array( $this, "parameterNotEmpty" ) ) ), $required ); if ( count( $intersection ) > 1 ) { - $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" ); + $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' ); } elseif ( count( $intersection ) == 0 ) { - $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', "{$p}missingparam" ); + $this->dieUsage( "One of the parameters {$p}" . implode( ", {$p}", $required ) . ' is required', 'missingparam' ); } } @@ -725,7 +724,7 @@ abstract class ApiBase extends ContextSource { array( $this, "parameterNotEmpty" ) ) ), $required ); if ( count( $intersection ) > 1 ) { - $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', "{$p}invalidparammix" ); + $this->dieUsage( "The parameters {$p}" . implode( ", {$p}", $intersection ) . ' can not be used together', 'invalidparammix' ); } } @@ -822,9 +821,9 @@ abstract class ApiBase extends ContextSource { * If not set will magically default to either watchdefault or watchcreations * @return bool */ - protected function getWatchlistValue ( $watchlist, $titleObj, $userOption = null ) { + protected function getWatchlistValue( $watchlist, $titleObj, $userOption = null ) { - $userWatching = $this->getUser()->isWatched( $titleObj ); + $userWatching = $this->getUser()->isWatched( $titleObj, WatchedItem::IGNORE_USER_RIGHTS ); switch ( $watchlist ) { case 'watch': @@ -866,12 +865,7 @@ abstract class ApiBase extends ContextSource { return; } - $user = $this->getUser(); - if ( $value ) { - WatchAction::doWatch( $titleObj, $user ); - } else { - WatchAction::doUnwatch( $titleObj, $user ); - } + WatchAction::doWatchOrUnwatch( $value, $titleObj, $this->getUser() ); } /** @@ -967,9 +961,9 @@ abstract class ApiBase extends ContextSource { } break; case 'integer': // Force everything using intval() and optionally validate limits - $min = isset ( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null; - $max = isset ( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null; - $enforceLimits = isset ( $paramSettings[self::PARAM_RANGE_ENFORCE] ) + $min = isset( $paramSettings[self::PARAM_MIN] ) ? $paramSettings[self::PARAM_MIN] : null; + $max = isset( $paramSettings[self::PARAM_MAX] ) ? $paramSettings[self::PARAM_MAX] : null; + $enforceLimits = isset( $paramSettings[self::PARAM_RANGE_ENFORCE] ) ? $paramSettings[self::PARAM_RANGE_ENFORCE] : false; if ( is_array( $value ) ) { @@ -1081,7 +1075,7 @@ abstract class ApiBase extends ContextSource { if ( !$allowMultiple && count( $valuesList ) != 1 ) { // Bug 33482 - Allow entries with | in them for non-multiple values - if ( in_array( $value, $allowedValues ) ) { + if ( in_array( $value, $allowedValues, true ) ) { return $value; } @@ -1165,7 +1159,7 @@ abstract class ApiBase extends ContextSource { /** * Validate and normalize of parameters of type 'user' * @param string $value Parameter value - * @param string $encParamName Parameter value + * @param string $encParamName Parameter name * @return string Validated and normalized parameter */ private function validateUser( $value, $encParamName ) { @@ -1223,6 +1217,44 @@ abstract class ApiBase extends ContextSource { } /** + * Throw a UsageException based on the errors in the Status object. + * + * @since 1.22 + * @param Status $status Status object + * @throws UsageException + */ + public function dieStatus( $status ) { + if ( $status->isGood() ) { + throw new MWException( 'Successful status passed to ApiBase::dieStatus' ); + } + + $errors = $status->getErrorsArray(); + if ( !$errors ) { + // No errors? Assume the warnings should be treated as errors + $errors = $status->getWarningsArray(); + } + if ( !$errors ) { + // Still no errors? Punt + $errors = array( array( 'unknownerror-nocode' ) ); + } + + // Cannot use dieUsageMsg() because extensions might return custom + // error messages. + if ( $errors[0] instanceof Message ) { + $msg = $errors[0]; + $code = $msg->getKey(); + } else { + $code = array_shift( $errors[0] ); + $msg = wfMessage( $code, $errors[0] ); + } + if ( isset( ApiBase::$messageMap[$code] ) ) { + // Translate message to code, for backwards compatability + $code = ApiBase::$messageMap[$code]['code']; + } + $this->dieUsage( $msg->inLanguage( 'en' )->useDatabase( false )->plain(), $code ); + } + + /** * Array that maps message keys to error messages. $1 and friends are replaced. */ public static $messageMap = array( @@ -1372,6 +1404,7 @@ abstract class ApiBase extends ContextSource { '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.' ), 'copyuploadbaddomain' => array( 'code' => 'copyuploadbaddomain', 'info' => 'Uploads by URL are not allowed from this domain.' ), + 'copyuploadbadurl' => array( 'code' => 'copyuploadbadurl', 'info' => 'Upload not allowed from this URL.' ), 'filename-tooshort' => array( 'code' => 'filename-tooshort', 'info' => 'The filename is too short' ), 'filename-toolong' => array( 'code' => 'filename-toolong', 'info' => 'The filename is too long' ), @@ -1397,7 +1430,7 @@ abstract class ApiBase extends ContextSource { 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 ) ) { + if ( is_string( $error ) ) { $error = array( $error ); } $parsed = $this->parseMsg( $error ); @@ -1412,10 +1445,10 @@ abstract class ApiBase extends ContextSource { */ public function dieUsageMsgOrDebug( $error ) { global $wgDebugAPI; - if( $wgDebugAPI !== true ) { + if ( $wgDebugAPI !== true ) { $this->dieUsageMsg( $error ); } else { - if( is_string( $error ) ) { + if ( is_string( $error ) ) { $error = array( $error ); } $parsed = $this->parseMsg( $error ); @@ -1448,7 +1481,7 @@ abstract class ApiBase extends ContextSource { // Check whether the error array was nested // array( array( <code>, <params> ), array( <another_code>, <params> ) ) - if( is_array( $key ) ) { + if ( is_array( $key ) ) { $error = $key; $key = array_shift( $error ); } @@ -1470,7 +1503,7 @@ abstract class ApiBase extends ContextSource { * @param string $message Error message */ protected static function dieDebug( $method, $message ) { - wfDebugDieBacktrace( "Internal error in $method: $message" ); + throw new MWException( "Internal error in $method: $message" ); } /** @@ -1535,7 +1568,7 @@ abstract class ApiBase extends ContextSource { public function getWatchlistUser( $params ) { if ( !is_null( $params['owner'] ) && !is_null( $params['token'] ) ) { $user = User::newFromName( $params['owner'], false ); - if ( !($user && $user->getId()) ) { + if ( !( $user && $user->getId() ) ) { $this->dieUsage( 'Specified user does not exist', 'bad_wlowner' ); } $token = $user->getOption( 'watchlisttoken' ); @@ -1546,6 +1579,9 @@ abstract class ApiBase extends ContextSource { if ( !$this->getUser()->isLoggedIn() ) { $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' ); } + if ( !$this->getUser()->isAllowed( 'viewmywatchlist' ) ) { + $this->dieUsage( 'You don\'t have permission to view your watchlist', 'permissiondenied' ); + } $user = $this->getUser(); } return $user; @@ -1560,6 +1596,10 @@ abstract class ApiBase extends ContextSource { /** * Returns a list of all possible errors returned by the module + * + * Don't call this function directly: use getFinalPossibleErrors() to allow + * hooks to modify parameters as needed. + * * @return array in the format of array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... ) */ public function getPossibleErrors() { @@ -1574,10 +1614,9 @@ abstract class ApiBase extends ContextSource { } if ( array_key_exists( 'continue', $params ) ) { $ret[] = array( - array( - 'code' => 'badcontinue', - 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' - ) ); + 'code' => 'badcontinue', + 'info' => 'Invalid continue param. You should pass the original value returned by the previous query' + ); } } @@ -1595,7 +1634,12 @@ abstract class ApiBase extends ContextSource { } if ( $this->needsToken() ) { - $ret[] = array( 'missingparam', 'token' ); + if ( !isset( $params['token'][ApiBase::PARAM_REQUIRED] ) + || !$params['token'][ApiBase::PARAM_REQUIRED] + ) { + // Add token as possible missing parameter, if not already done + $ret[] = array( 'missingparam', 'token' ); + } $ret[] = array( 'sessionfailure' ); } @@ -1603,6 +1647,19 @@ abstract class ApiBase extends ContextSource { } /** + * Get final list of possible errors, after hooks have had a chance to + * tweak it as needed. + * + * @return array + * @since 1.22 + */ + public function getFinalPossibleErrors() { + $possibleErrors = $this->getPossibleErrors(); + wfRunHooks( 'APIGetPossibleErrors', array( $this, &$possibleErrors ) ); + return $possibleErrors; + } + + /** * Parses a list of errors into a standardised format * @param array $errors List of errors. Items can be in the for array( key, param1, param2, ... ) or array( 'code' => ..., 'info' => ... ) * @return array Parsed list of errors with items in the form array( 'code' => ..., 'info' => ... ) diff --git a/includes/api/ApiBlock.php b/includes/api/ApiBlock.php index 6f3d1e4f..975153ac 100644 --- a/includes/api/ApiBlock.php +++ b/includes/api/ApiBlock.php @@ -98,7 +98,7 @@ class ApiBlock extends ApiBase { $res['userID'] = $target instanceof User ? $target->getId() : 0; $block = Block::newFromTarget( $target ); - if( $block instanceof Block ) { + if ( $block instanceof Block ) { $res['expiry'] = $block->mExpiry == $this->getDB()->getInfinity() ? 'infinite' : wfTimestamp( TS_ISO_8601, $block->mExpiry ); diff --git a/includes/api/ApiComparePages.php b/includes/api/ApiComparePages.php index 79ffcb0a..1e35c349 100644 --- a/includes/api/ApiComparePages.php +++ b/includes/api/ApiComparePages.php @@ -81,17 +81,17 @@ class ApiComparePages extends ApiBase { * @return int */ private function revisionOrTitleOrId( $revision, $titleText, $titleId ) { - if( $revision ) { + if ( $revision ) { return $revision; - } elseif( $titleText ) { + } elseif ( $titleText ) { $title = Title::newFromText( $titleText ); - if( !$title || $title->isExternal() ) { + if ( !$title || $title->isExternal() ) { $this->dieUsageMsg( array( 'invalidtitle', $titleText ) ); } return $title->getLatestRevID(); } elseif ( $titleId ) { $title = Title::newFromID( $titleId ); - if( !$title ) { + if ( !$title ) { $this->dieUsageMsg( array( 'nosuchpageid', $titleId ) ); } return $title->getLatestRevID(); diff --git a/includes/api/ApiCreateAccount.php b/includes/api/ApiCreateAccount.php index 69748c93..0e752c56 100644 --- a/includes/api/ApiCreateAccount.php +++ b/includes/api/ApiCreateAccount.php @@ -47,17 +47,19 @@ class ApiCreateAccount extends ApiBase { $params = $this->extractRequestParams(); - $result = array(); - // Init session if necessary if ( session_id() == '' ) { wfSetupSession(); } - if( $params['mailpassword'] && !$params['email'] ) { + if ( $params['mailpassword'] && !$params['email'] ) { $this->dieUsageMsg( 'noemail' ); } + if ( $params['language'] && !Language::isSupportedLanguage( $params['language'] ) ) { + $this->dieUsage( 'Invalid language parameter', 'langinvalid' ); + } + $context = new DerivativeContext( $this->getContext() ); $context->setRequest( new DerivativeRequest( $this->getContext()->getRequest(), @@ -82,22 +84,20 @@ class ApiCreateAccount extends ApiBase { $status = $loginForm->addNewaccountInternal(); $result = array(); - if( $status->isGood() ) { + if ( $status->isGood() ) { // Success! + global $wgEmailAuthentication; $user = $status->getValue(); - // If we showed up language selection links, and one was in use, be - // smart (and sensible) and save that language as the user's preference - global $wgLoginLanguageSelector, $wgEmailAuthentication; - if( $wgLoginLanguageSelector && $params['language'] ) { + if ( $params['language'] ) { $user->setOption( 'language', $params['language'] ); } - if( $params['mailpassword'] ) { + if ( $params['mailpassword'] ) { // If mailpassword was set, disable the password and send an email. $user->setPassword( null ); $status->merge( $loginForm->mailPasswordInternal( $user, false, 'createaccount-title', 'createaccount-text' ) ); - } elseif( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) { + } elseif ( $wgEmailAuthentication && Sanitizer::validateEmail( $user->getEmail() ) ) { // Send out an email authentication message if needed $status->merge( $user->sendConfirmationMail() ); } @@ -124,33 +124,23 @@ class ApiCreateAccount extends ApiBase { $apiResult = $this->getResult(); - if( $status->hasMessage( 'sessionfailure' ) || $status->hasMessage( 'nocookiesfornew' ) ) { + if ( $status->hasMessage( 'sessionfailure' ) || $status->hasMessage( 'nocookiesfornew' ) ) { // Token was incorrect, so add it to result, but don't throw an exception // since not having the correct token is part of the normal // flow of events. $result['token'] = LoginForm::getCreateaccountToken(); $result['result'] = 'needtoken'; - } elseif( !$status->isOK() ) { + } elseif ( !$status->isOK() ) { // There was an error. Die now. - // Cannot use dieUsageMsg() directly because extensions - // might return custom error messages. - $errors = $status->getErrorsArray(); - if( $errors[0] instanceof Message ) { - $code = 'aborted'; - $desc = $errors[0]; - } else { - $code = array_shift( $errors[0] ); - $desc = wfMessage( $code, $errors[0] ); - } - $this->dieUsage( $desc, $code ); - } elseif( !$status->isGood() ) { + $this->dieStatus( $status ); + } elseif ( !$status->isGood() ) { // Status is not good, but OK. This means warnings. $result['result'] = 'warning'; // Add any warnings to the result $warnings = $status->getErrorsByType( 'warning' ); - if( $warnings ) { - foreach( $warnings as &$warning ) { + if ( $warnings ) { + foreach ( $warnings as &$warning ) { $apiResult->setIndexedTagName( $warning['params'], 'param' ); } $apiResult->setIndexedTagName( $warnings, 'warning' ); @@ -263,8 +253,8 @@ class ApiCreateAccount extends ApiBase { $errors = parent::getPossibleErrors(); // All local errors are from LoginForm, which means they're actually message keys. - foreach( $localErrors as $error ) { - $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->parse() ); + foreach ( $localErrors as $error ) { + $errors[] = array( 'code' => $error, 'info' => wfMessage( $error )->inLanguage( 'en' )->useDatabase( false )->parse() ); } $errors[] = array( @@ -279,12 +269,16 @@ class ApiCreateAccount extends ApiBase { 'code' => 'aborted', 'info' => 'Account creation aborted by hook (info may vary)' ); + $errors[] = array( + 'code' => 'langinvalid', + 'info' => 'Invalid language parameter' + ); // 'passwordtooshort' has parameters. :( global $wgMinimalPasswordLength; $errors[] = array( 'code' => 'passwordtooshort', - 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->parse() + 'info' => wfMessage( 'passwordtooshort', $wgMinimalPasswordLength )->inLanguage( 'en' )->useDatabase( false )->parse() ); return $errors; } diff --git a/includes/api/ApiDelete.php b/includes/api/ApiDelete.php index d1f0806e..aea10482 100644 --- a/includes/api/ApiDelete.php +++ b/includes/api/ApiDelete.php @@ -61,8 +61,7 @@ class ApiDelete extends ApiBase { $this->dieUsageMsg( $status[0] ); } if ( !$status->isGood() ) { - $errors = $status->getErrorsArray(); - $this->dieUsageMsg( $errors[0] ); // We don't care about multiple errors, just report one of them + $this->dieStatus( $status ); } // Deprecated parameters diff --git a/includes/api/ApiEditPage.php b/includes/api/ApiEditPage.php index 4916145b..bd61895b 100644 --- a/includes/api/ApiEditPage.php +++ b/includes/api/ApiEditPage.php @@ -100,7 +100,7 @@ class ApiEditPage extends ApiBase { $name = $titleObj->getPrefixedDBkey(); $model = $contentHandler->getModelID(); - $this->dieUsage( "The requested format $contentFormat is not supported for content model ". + $this->dieUsage( "The requested format $contentFormat is not supported for content model " . " $model used by $name", 'badformat' ); } @@ -146,7 +146,7 @@ class ApiEditPage extends ApiBase { } } - // @todo: Add support for appending/prepending to the Content interface + // @todo Add support for appending/prepending to the Content interface if ( !( $content instanceof TextContent ) ) { $mode = $contentHandler->getModelID(); @@ -159,12 +159,17 @@ class ApiEditPage extends ApiBase { $this->dieUsage( "Sections are not supported for this content model: $modelName.", 'sectionsnotsupported' ); } - // Process the content for section edits - $section = intval( $params['section'] ); - $content = $content->getSection( $section ); + if ( $params['section'] == 'new' ) { + // DWIM if they're trying to prepend/append to a new section. + $content = null; + } else { + // Process the content for section edits + $section = intval( $params['section'] ); + $content = $content->getSection( $section ); - if ( !$content ) { - $this->dieUsage( "There is no section {$section}.", 'nosuchsection' ); + if ( !$content ) { + $this->dieUsage( "There is no section {$section}.", 'nosuchsection' ); + } } } @@ -262,7 +267,7 @@ class ApiEditPage extends ApiBase { $requestArray['wpStarttime'] = wfTimestampNow(); // Fake wpStartime } - if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) { + if ( $params['minor'] || ( !$params['notminor'] && $user->getOption( 'minordefault' ) ) ) { $requestArray['wpMinoredit'] = ''; } @@ -275,6 +280,10 @@ class ApiEditPage extends ApiBase { if ( $section == 0 && $params['section'] != '0' && $params['section'] != 'new' ) { $this->dieUsage( "The section parameter must be set to an integer or 'new'", "invalidsection" ); } + $content = $pageObj->getContent(); + if ( $section !== 0 && ( !$content || !$content->getSection( $section ) ) ) { + $this->dieUsage( "There is no section {$section}.", 'nosuchsection' ); + } $requestArray['wpSection'] = $params['section']; } else { $requestArray['wpSection'] = ''; @@ -293,6 +302,10 @@ class ApiEditPage extends ApiBase { $requestArray['wpWatchthis'] = ''; } + // Pass through anything else we might have been given, to support extensions + // This is kind of a hack but it's the best we can do to make extensions work + $requestArray += $this->getRequest()->getValues(); + global $wgTitle, $wgRequest; $req = new DerivativeRequest( $this->getRequest(), $requestArray, true ); @@ -316,11 +329,37 @@ class ApiEditPage extends ApiBase { $ep->setContextTitle( $titleObj ); $ep->importFormData( $req ); + $content = $ep->textbox1; + + // The following is needed to give the hook the full content of the + // new revision rather than just the current section. (Bug 52077) + if ( !is_null( $params['section'] ) && $contentHandler->supportsSections() && $titleObj->exists() ) { + + $sectionTitle = ''; + // If sectiontitle is set, use it, otherwise use the summary as the section title (for + // backwards compatibility with old forms/bots). + if ( $ep->sectiontitle !== '' ) { + $sectionTitle = $ep->sectiontitle; + } else { + $sectionTitle = $ep->summary; + } + + $contentObj = $contentHandler->unserializeContent( $content, $contentFormat ); + + $fullContentObj = $articleObject->replaceSectionContent( $params['section'], $contentObj, $sectionTitle ); + if ( $fullContentObj ) { + $content = $fullContentObj->serialize( $contentFormat ); + } else { + // This most likely means we have an edit conflict which means that the edit + // wont succeed anyway. + $this->dieUsageMsg( 'editconflict' ); + } + } // Run hooks // Handle APIEditBeforeSave parameters $r = array(); - if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $ep->textbox1, &$r ) ) ) { + if ( !wfRunHooks( 'APIEditBeforeSave', array( $ep, $content, &$r ) ) ) { if ( count( $r ) ) { $r['result'] = 'Failure'; $apiResult->addValue( null, $this->getModuleName(), $r ); @@ -342,7 +381,7 @@ class ApiEditPage extends ApiBase { $wgRequest = $oldRequest; global $wgMaxArticleSize; - switch( $status->value ) { + switch ( $status->value ) { case EditPage::AS_HOOK_ERROR: case EditPage::AS_HOOK_ERROR_EXPECTED: $this->dieUsageMsg( 'hookaborted' ); diff --git a/includes/api/ApiExpandTemplates.php b/includes/api/ApiExpandTemplates.php index f5898fb3..d5c789c3 100644 --- a/includes/api/ApiExpandTemplates.php +++ b/includes/api/ApiExpandTemplates.php @@ -65,14 +65,14 @@ class ApiExpandTemplates extends ApiBase { $xml = $dom->__toString(); } $xml_result = array(); - $result->setContent( $xml_result, $xml ); + ApiResult::setContent( $xml_result, $xml ); $result->addValue( null, 'parsetree', $xml_result ); } $retval = $wgParser->preprocess( $params['text'], $title_obj, $options ); // Return result $retval_array = array(); - $result->setContent( $retval_array, $retval ); + ApiResult::setContent( $retval_array, $retval ); $result->addValue( null, $this->getModuleName(), $retval_array ); } diff --git a/includes/api/ApiFeedContributions.php b/includes/api/ApiFeedContributions.php index 015a9922..05691093 100644 --- a/includes/api/ApiFeedContributions.php +++ b/includes/api/ApiFeedContributions.php @@ -43,11 +43,11 @@ class ApiFeedContributions extends ApiBase { global $wgFeed, $wgFeedClasses, $wgSitename, $wgLanguageCode; - if( !$wgFeed ) { + if ( !$wgFeed ) { $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' ); } - if( !isset( $wgFeedClasses[$params['feedformat']] ) ) { + if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) { $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' ); } @@ -82,7 +82,7 @@ class ApiFeedContributions extends ApiBase { ) ); $feedItems = array(); - if( $pager->getNumRows() > 0 ) { + if ( $pager->getNumRows() > 0 ) { foreach ( $pager->mResult as $row ) { $feedItems[] = $this->feedItem( $row ); } @@ -93,7 +93,7 @@ class ApiFeedContributions extends ApiBase { protected function feedItem( $row ) { $title = Title::makeTitle( intval( $row->page_namespace ), $row->page_title ); - if( $title ) { + if ( $title && $title->userCan( 'read', $this->getUser() ) ) { $date = $row->rev_timestamp; $comments = $title->getTalkPage()->getFullURL(); $revision = Revision::newFromRow( $row ); @@ -106,9 +106,8 @@ class ApiFeedContributions extends ApiBase { $this->feedItemAuthor( $revision ), $comments ); - } else { - return null; } + return null; } /** @@ -124,7 +123,7 @@ class ApiFeedContributions extends ApiBase { * @return string */ protected function feedItemDesc( $revision ) { - if( $revision ) { + if ( $revision ) { $msg = wfMessage( 'colon-separator' )->inContentLanguage()->text(); $content = $revision->getContent(); @@ -149,7 +148,7 @@ class ApiFeedContributions extends ApiBase { public function getAllowedParams() { global $wgFeedClasses; $feedFormatNames = array_keys( $wgFeedClasses ); - return array ( + return array( 'feedformat' => array( ApiBase::PARAM_DFLT => 'rss', ApiBase::PARAM_TYPE => $feedFormatNames diff --git a/includes/api/ApiFeedWatchlist.php b/includes/api/ApiFeedWatchlist.php index 6c793b36..fbb70fbc 100644 --- a/includes/api/ApiFeedWatchlist.php +++ b/includes/api/ApiFeedWatchlist.php @@ -33,6 +33,10 @@ */ class ApiFeedWatchlist extends ApiBase { + private $watchlistModule = null; + private $linkToDiffs = false; + private $linkToSections = false; + /** * This module uses a custom feed wrapper printer. * @@ -42,8 +46,6 @@ class ApiFeedWatchlist extends ApiBase { return new ApiFormatFeedWrapper( $this->getMain() ); } - private $linkToDiffs = false; - /** * Make a nested call to the API to request watchlist items in the last $hours. * Wrap the result as an RSS/Atom feed. @@ -54,11 +56,11 @@ class ApiFeedWatchlist extends ApiBase { try { $params = $this->extractRequestParams(); - if( !$wgFeed ) { + if ( !$wgFeed ) { $this->dieUsage( 'Syndication feeds are not available', 'feed-unavailable' ); } - if( !isset( $wgFeedClasses[$params['feedformat']] ) ) { + if ( !isset( $wgFeedClasses[$params['feedformat']] ) ) { $this->dieUsage( 'Invalid subscription feed type', 'feed-invalid' ); } @@ -74,7 +76,7 @@ class ApiFeedWatchlist extends ApiBase { 'wlprop' => 'title|user|comment|timestamp', 'wldir' => 'older', // reverse order - from newest to oldest 'wlend' => $endTime, // stop at this time - 'wllimit' => ( 50 > $wgFeedLimit ) ? $wgFeedLimit : 50 + 'wllimit' => min( 50, $wgFeedLimit ) ); if ( $params['wlowner'] !== null ) { @@ -86,6 +88,12 @@ class ApiFeedWatchlist extends ApiBase { if ( $params['wlexcludeuser'] !== null ) { $fauxReqArr['wlexcludeuser'] = $params['wlexcludeuser']; } + if ( $params['wlshow'] !== null ) { + $fauxReqArr['wlshow'] = $params['wlshow']; + } + if ( $params['wltype'] !== null ) { + $fauxReqArr['wltype'] = $params['wltype']; + } // Support linking to diffs instead of article if ( $params['linktodiffs'] ) { @@ -93,6 +101,12 @@ class ApiFeedWatchlist extends ApiBase { $fauxReqArr['wlprop'] .= '|ids'; } + // Support linking directly to sections when possible + // (possible only if section name is present in comment) + if ( $params['linktosections'] ) { + $this->linkToSections = true; + } + // Check for 'allrev' parameter, and if found, show all revisions to each page on wl. if ( $params['allrev'] ) { $fauxReqArr['wlallrev'] = ''; @@ -160,6 +174,18 @@ class ApiFeedWatchlist extends ApiBase { $titleUrl = $title->getFullURL(); } $comment = isset( $info['comment'] ) ? $info['comment'] : null; + + // Create an anchor to section. + // The anchor won't work for sections that have dupes on page + // as there's no way to strip that info from ApiWatchlist (apparently?). + // RegExp in the line below is equal to Linker::formatAutocomments(). + if ( $this->linkToSections && $comment !== null && preg_match( '!(.*)/\*\s*(.*?)\s*\*/(.*)!', $comment, $matches ) ) { + global $wgParser; + $sectionTitle = $wgParser->stripSectionName( $matches[2] ); + $sectionTitle = Sanitizer::normalizeSectionNameWhitespace( $sectionTitle ); + $titleUrl .= Title::newFromText( '#' . $sectionTitle )->getFragmentForURL(); + } + $timestamp = $info['timestamp']; $user = $info['user']; @@ -168,10 +194,18 @@ class ApiFeedWatchlist extends ApiBase { return new FeedItem( $titleStr, $completeText, $titleUrl, $timestamp, $user ); } - public function getAllowedParams() { + private function getWatchlistModule() { + if ( $this->watchlistModule === null ) { + $this->watchlistModule = $this->getMain()->getModuleManager()->getModule( 'query' ) + ->getModuleManager()->getModule( 'watchlist' ); + } + return $this->watchlistModule; + } + + public function getAllowedParams( $flags = 0 ) { global $wgFeedClasses; $feedFormatNames = array_keys( $wgFeedClasses ); - return array ( + $ret = array( 'feedformat' => array( ApiBase::PARAM_DFLT => 'rss', ApiBase::PARAM_TYPE => $feedFormatNames @@ -182,29 +216,41 @@ class ApiFeedWatchlist extends ApiBase { ApiBase::PARAM_MIN => 1, ApiBase::PARAM_MAX => 72, ), - 'allrev' => false, - 'wlowner' => array( - ApiBase::PARAM_TYPE => 'user' - ), - 'wltoken' => array( - ApiBase::PARAM_TYPE => 'string' - ), - 'wlexcludeuser' => array( - ApiBase::PARAM_TYPE => 'user' - ), 'linktodiffs' => false, + 'linktosections' => false, ); + if ( $flags ) { + $wlparams = $this->getWatchlistModule()->getAllowedParams( $flags ); + $ret['allrev'] = $wlparams['allrev']; + $ret['wlowner'] = $wlparams['owner']; + $ret['wltoken'] = $wlparams['token']; + $ret['wlshow'] = $wlparams['show']; + $ret['wltype'] = $wlparams['type']; + $ret['wlexcludeuser'] = $wlparams['excludeuser']; + } else { + $ret['allrev'] = null; + $ret['wlowner'] = null; + $ret['wltoken'] = null; + $ret['wlshow'] = null; + $ret['wltype'] = null; + $ret['wlexcludeuser'] = null; + } + return $ret; } public function getParamDescription() { + $wldescr = $this->getWatchlistModule()->getParamDescription(); return array( 'feedformat' => 'The format of the feed', - 'hours' => 'List pages modified within this many hours from now', - 'allrev' => 'Include multiple revisions of the same page within given timeframe', - 'wlowner' => "The user whose watchlist you want (must be accompanied by {$this->getModulePrefix()}wltoken if it's not you)", - 'wltoken' => 'Security token that requested user set in their preferences', - 'wlexcludeuser' => 'A user whose edits should not be shown in the watchlist', + 'hours' => 'List pages modified within this many hours from now', 'linktodiffs' => 'Link to change differences instead of article pages', + 'linktosections' => 'Link directly to changed sections if possible', + 'allrev' => $wldescr['allrev'], + 'wlowner' => $wldescr['owner'], + 'wltoken' => $wldescr['token'], + 'wlshow' => $wldescr['show'], + 'wltype' => $wldescr['type'], + 'wlexcludeuser' => $wldescr['excludeuser'], ); } diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index d8aa1634..b89fb3a7 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -85,7 +85,7 @@ abstract class ApiFormatBase extends ApiBase { * * @param bool $b Whether or not ampersands should be escaped. */ - public function setUnescapeAmps ( $b ) { + public function setUnescapeAmps( $b ) { $this->mUnescapeAmps = $b; } @@ -170,12 +170,12 @@ abstract class ApiFormatBase extends ApiBase { ?> <br /> <small> -You are looking at the HTML representation of the <?php echo( $this->mFormat ); ?> format.<br /> +You are looking at the HTML representation of the <?php echo $this->mFormat; ?> format.<br /> HTML is good for debugging, but is unsuitable for application use.<br /> Specify the format parameter to change the output format.<br /> -To see the non HTML representation of the <?php echo( $this->mFormat ); ?> format, set format=<?php echo( strtolower( $this->mFormat ) ); ?>.<br /> +To see the non HTML representation of the <?php echo $this->mFormat; ?> format, set format=<?php echo strtolower( $this->mFormat ); ?>.<br /> See the <a href='https://www.mediawiki.org/wiki/API'>complete documentation</a>, or -<a href='<?php echo( $script ); ?>'>API help</a> for more information. +<a href='<?php echo $script; ?>'>API help</a> for more information. </small> <pre style='white-space: pre-wrap;'> <?php diff --git a/includes/api/ApiFormatJson.php b/includes/api/ApiFormatJson.php index abb63480..342a580f 100644 --- a/includes/api/ApiFormatJson.php +++ b/includes/api/ApiFormatJson.php @@ -56,30 +56,32 @@ class ApiFormatJson extends ApiFormatBase { } public function execute() { - $prefix = $suffix = ''; - $params = $this->extractRequestParams(); + $json = FormatJson::encode( + $this->getResultData(), + $this->getIsHtml(), + $params['utf8'] ? FormatJson::ALL_OK : FormatJson::XMLMETA_OK + ); $callback = $params['callback']; - if ( !is_null( $callback ) ) { - $prefix = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback ) . '('; - $suffix = ')'; + if ( $callback !== null ) { + $callback = preg_replace( "/[^][.\\'\\\"_A-Za-z0-9]/", '', $callback ); + $this->printText( "$callback($json)" ); + } else { + $this->printText( $json ); } - $this->printText( - $prefix . - FormatJson::encode( $this->getResultData(), $this->getIsHtml() ) . - $suffix - ); } public function getAllowedParams() { return array( - 'callback' => null, + 'callback' => null, + 'utf8' => false, ); } public function getParamDescription() { return array( 'callback' => 'If specified, wraps the output into a given function call. For safety, all user-specific data will be restricted.', + 'utf8' => 'If specified, encodes most (but not all) non-ASCII characters as UTF-8 instead of replacing them with hexadecimal escape sequences.', ); } diff --git a/includes/api/ApiFormatWddx.php b/includes/api/ApiFormatWddx.php index 62b69bb6..5685d937 100644 --- a/includes/api/ApiFormatWddx.php +++ b/includes/api/ApiFormatWddx.php @@ -46,7 +46,7 @@ class ApiFormatWddx extends ApiFormatBase { } else { // Don't do newlines and indentation if we weren't asked // for pretty output - $nl = ( $this->getIsHtml() ? '' : "\n" ); + $nl = ( $this->getIsHtml() ? "\n" : '' ); $indstr = ' '; $this->printText( "<?xml version=\"1.0\"?>$nl" ); $this->printText( "<wddxPacket version=\"1.0\">$nl" ); @@ -64,44 +64,43 @@ class ApiFormatWddx extends ApiFormatBase { * @param $indent int */ function slowWddxPrinter( $elemValue, $indent = 0 ) { - $indstr = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent ) ); - $indstr2 = ( $this->getIsHtml() ? '' : str_repeat( ' ', $indent + 2 ) ); - $nl = ( $this->getIsHtml() ? '' : "\n" ); - switch ( gettype( $elemValue ) ) { - case 'array': - // Check whether we've got an associative array (<struct>) - // or a regular array (<array>) - $cnt = count( $elemValue ); - if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) { - // Regular array - $this->printText( $indstr . Xml::element( 'array', array( - 'length' => $cnt ), null ) . $nl ); - foreach ( $elemValue as $subElemValue ) { - $this->slowWddxPrinter( $subElemValue, $indent + 2 ); - } - $this->printText( "$indstr</array>$nl" ); - } else { - // Associative array (<struct>) - $this->printText( "$indstr<struct>$nl" ); - foreach ( $elemValue as $subElemName => $subElemValue ) { - $this->printText( $indstr2 . Xml::element( 'var', array( - 'name' => $subElemName - ), null ) . $nl ); - $this->slowWddxPrinter( $subElemValue, $indent + 4 ); - $this->printText( "$indstr2</var>$nl" ); - } - $this->printText( "$indstr</struct>$nl" ); + $indstr = ( $this->getIsHtml() ? str_repeat( ' ', $indent ) : '' ); + $indstr2 = ( $this->getIsHtml() ? str_repeat( ' ', $indent + 2 ) : '' ); + $nl = ( $this->getIsHtml() ? "\n" : '' ); + if ( is_array( $elemValue ) ) { + // Check whether we've got an associative array (<struct>) + // or a regular array (<array>) + $cnt = count( $elemValue ); + if ( $cnt == 0 || array_keys( $elemValue ) === range( 0, $cnt - 1 ) ) { + // Regular array + $this->printText( $indstr . Xml::element( 'array', array( + 'length' => $cnt ), null ) . $nl ); + foreach ( $elemValue as $subElemValue ) { + $this->slowWddxPrinter( $subElemValue, $indent + 2 ); } - break; - case 'integer': - case 'double': - $this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl ); - break; - case 'string': - $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl ); - break; - default: - ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) ); + $this->printText( "$indstr</array>$nl" ); + } else { + // Associative array (<struct>) + $this->printText( "$indstr<struct>$nl" ); + foreach ( $elemValue as $subElemName => $subElemValue ) { + $this->printText( $indstr2 . Xml::element( 'var', array( + 'name' => $subElemName + ), null ) . $nl ); + $this->slowWddxPrinter( $subElemValue, $indent + 4 ); + $this->printText( "$indstr2</var>$nl" ); + } + $this->printText( "$indstr</struct>$nl" ); + } + } elseif ( is_int( $elemValue ) || is_float( $elemValue ) ) { + $this->printText( $indstr . Xml::element( 'number', null, $elemValue ) . $nl ); + } elseif ( is_string( $elemValue ) ) { + $this->printText( $indstr . Xml::element( 'string', null, $elemValue ) . $nl ); + } elseif ( is_bool( $elemValue ) ) { + $this->printText( $indstr . Xml::element( 'boolean', + array( 'value' => $elemValue ? 'true' : 'false' ) ) . $nl + ); + } else { + ApiBase::dieDebug( __METHOD__, 'Unknown type ' . gettype( $elemValue ) ); } } diff --git a/includes/api/ApiFormatXml.php b/includes/api/ApiFormatXml.php index b4e8e330..4ec149c0 100644 --- a/includes/api/ApiFormatXml.php +++ b/includes/api/ApiFormatXml.php @@ -32,7 +32,6 @@ class ApiFormatXml extends ApiFormatBase { private $mRootElemName = 'api'; public static $namespace = 'http://www.mediawiki.org/xml/api/'; - private $mDoubleQuote = false; private $mIncludeNamespace = false; private $mXslt = null; @@ -50,7 +49,6 @@ class ApiFormatXml extends ApiFormatBase { public function execute() { $params = $this->extractRequestParams(); - $this->mDoubleQuote = $params['xmldoublequote']; $this->mIncludeNamespace = $params['includexmlnamespace']; $this->mXslt = $params['xslt']; @@ -71,8 +69,7 @@ class ApiFormatXml extends ApiFormatBase { $this->printText( self::recXmlPrint( $this->mRootElemName, $data, - $this->getIsHtml() ? - 2 : null, - $this->mDoubleQuote + $this->getIsHtml() ? - 2 : null ) ); } @@ -117,11 +114,10 @@ class ApiFormatXml extends ApiFormatBase { * @param $elemName * @param $elemValue * @param $indent - * @param $doublequote bool * * @return string */ - public static function recXmlPrint( $elemName, $elemValue, $indent, $doublequote = false ) { + public static function recXmlPrint( $elemName, $elemValue, $indent ) { $retval = ''; if ( !is_null( $indent ) ) { $indent += 2; @@ -131,84 +127,71 @@ class ApiFormatXml extends ApiFormatBase { } $elemName = str_replace( ' ', '_', $elemName ); - switch ( gettype( $elemValue ) ) { - case 'array': - if ( isset( $elemValue['*'] ) ) { - $subElemContent = $elemValue['*']; - if ( $doublequote ) { - $subElemContent = Sanitizer::encodeAttribute( $subElemContent ); - } - unset( $elemValue['*'] ); - - // Add xml:space="preserve" to the - // element so XML parsers will leave - // whitespace in the content alone - $elemValue['xml:space'] = 'preserve'; - } else { - $subElemContent = null; + if ( is_array( $elemValue ) ) { + if ( isset( $elemValue['*'] ) ) { + $subElemContent = $elemValue['*']; + unset( $elemValue['*'] ); + + // Add xml:space="preserve" to the + // element so XML parsers will leave + // whitespace in the content alone + $elemValue['xml:space'] = 'preserve'; + } else { + $subElemContent = null; + } + + if ( isset( $elemValue['_element'] ) ) { + $subElemIndName = $elemValue['_element']; + unset( $elemValue['_element'] ); + } else { + $subElemIndName = null; + } + + $indElements = array(); + $subElements = array(); + foreach ( $elemValue as $subElemId => & $subElemValue ) { + if ( is_int( $subElemId ) ) { + $indElements[] = $subElemValue; + unset( $elemValue[$subElemId] ); + } elseif ( is_array( $subElemValue ) ) { + $subElements[$subElemId] = $subElemValue; + unset( $elemValue[$subElemId] ); } + } - if ( isset( $elemValue['_element'] ) ) { - $subElemIndName = $elemValue['_element']; - unset( $elemValue['_element'] ); - } else { - $subElemIndName = null; - } + if ( is_null( $subElemIndName ) && count( $indElements ) ) { + ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." ); + } - $indElements = array(); - $subElements = array(); - foreach ( $elemValue as $subElemId => & $subElemValue ) { - if ( is_string( $subElemValue ) && $doublequote ) { - $subElemValue = Sanitizer::encodeAttribute( $subElemValue ); - } - - if ( gettype( $subElemId ) === 'integer' ) { - $indElements[] = $subElemValue; - unset( $elemValue[$subElemId] ); - } elseif ( is_array( $subElemValue ) ) { - $subElements[$subElemId] = $subElemValue; - unset ( $elemValue[$subElemId] ); - } - } + if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) { + ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" ); + } - if ( is_null( $subElemIndName ) && count( $indElements ) ) { - ApiBase::dieDebug( __METHOD__, "($elemName, ...) has integer keys without _element value. Use ApiResult::setIndexedTagName()." ); - } + if ( !is_null( $subElemContent ) ) { + $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent ); + } elseif ( !count( $indElements ) && !count( $subElements ) ) { + $retval .= $indstr . Xml::element( $elemName, $elemValue ); + } else { + $retval .= $indstr . Xml::element( $elemName, $elemValue, null ); - if ( count( $subElements ) && count( $indElements ) && !is_null( $subElemContent ) ) { - ApiBase::dieDebug( __METHOD__, "($elemName, ...) has content and subelements" ); + foreach ( $subElements as $subElemId => & $subElemValue ) { + $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent ); } - if ( !is_null( $subElemContent ) ) { - $retval .= $indstr . Xml::element( $elemName, $elemValue, $subElemContent ); - } elseif ( !count( $indElements ) && !count( $subElements ) ) { - $retval .= $indstr . Xml::element( $elemName, $elemValue ); - } else { - $retval .= $indstr . Xml::element( $elemName, $elemValue, null ); - - foreach ( $subElements as $subElemId => & $subElemValue ) { - $retval .= self::recXmlPrint( $subElemId, $subElemValue, $indent ); - } - - foreach ( $indElements as &$subElemValue ) { - $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent ); - } - - $retval .= $indstr . Xml::closeElement( $elemName ); + foreach ( $indElements as &$subElemValue ) { + $retval .= self::recXmlPrint( $subElemIndName, $subElemValue, $indent ); } - break; - case 'object': - // ignore - break; - default: - // to make sure null value doesn't produce unclosed element, - // which is what Xml::element( $elemName, null, null ) returns - if ( $elemValue === null ) { - $retval .= $indstr . Xml::element( $elemName ); - } else { - $retval .= $indstr . Xml::element( $elemName, null, $elemValue ); - } - break; + + $retval .= $indstr . Xml::closeElement( $elemName ); + } + } elseif ( !is_object( $elemValue ) ) { + // to make sure null value doesn't produce unclosed element, + // which is what Xml::element( $elemName, null, null ) returns + if ( $elemValue === null ) { + $retval .= $indstr . Xml::element( $elemName ); + } else { + $retval .= $indstr . Xml::element( $elemName, null, $elemValue ); + } } return $retval; } @@ -232,7 +215,6 @@ class ApiFormatXml extends ApiFormatBase { public function getAllowedParams() { return array( - 'xmldoublequote' => false, 'xslt' => null, 'includexmlnamespace' => false, ); @@ -240,7 +222,6 @@ class ApiFormatXml extends ApiFormatBase { public function getParamDescription() { return array( - 'xmldoublequote' => 'If specified, double quotes all attributes and content', 'xslt' => 'If specified, adds <xslt> as stylesheet. This should be a wiki page ' . 'in the MediaWiki namespace whose page name ends with ".xsl"', 'includexmlnamespace' => 'If specified, adds an XML namespace' diff --git a/includes/api/ApiImageRotate.php b/includes/api/ApiImageRotate.php index b2d75825..7a60e831 100644 --- a/includes/api/ApiImageRotate.php +++ b/includes/api/ApiImageRotate.php @@ -22,7 +22,6 @@ */ class ApiImageRotate extends ApiBase { - private $mPageSet = null; public function __construct( $main, $action ) { @@ -38,32 +37,29 @@ class ApiImageRotate extends ApiBase { */ private static function addValues( array &$result, $values, $flag = null, $name = null ) { foreach ( $values as $val ) { - if( $val instanceof Title ) { + if ( $val instanceof Title ) { $v = array(); ApiQueryBase::addTitleInfo( $v, $val ); - } elseif( $name !== null ) { + } elseif ( $name !== null ) { $v = array( $name => $val ); } else { $v = $val; } - if( $flag !== null ) { + if ( $flag !== null ) { $v[$flag] = ''; } $result[] = $v; } } - public function execute() { $params = $this->extractRequestParams(); - $rotation = $params[ 'rotation' ]; - $user = $this->getUser(); + $rotation = $params['rotation']; $pageSet = $this->getPageSet(); $pageSet->execute(); $result = array(); - $result = array(); self::addValues( $result, $pageSet->getInvalidTitles(), 'invalid', 'title' ); self::addValues( $result, $pageSet->getSpecialTitles(), 'special', 'title' ); @@ -111,15 +107,17 @@ class ApiImageRotate extends ApiBase { continue; } $ext = strtolower( pathinfo( "$srcPath", PATHINFO_EXTENSION ) ); - $tmpFile = TempFSFile::factory( 'rotate_', $ext); + $tmpFile = TempFSFile::factory( 'rotate_', $ext ); $dstPath = $tmpFile->getPath(); $err = $handler->rotate( $file, array( "srcPath" => $srcPath, "dstPath" => $dstPath, - "rotation"=> $rotation + "rotation" => $rotation ) ); if ( !$err ) { - $comment = wfMessage( 'rotate-comment' )->numParams( $rotation )->text(); + $comment = wfMessage( + 'rotate-comment' + )->numParams( $rotation )->inContentLanguage()->text(); $status = $file->upload( $dstPath, $comment, $comment, 0, false, false, $this->getUser() ); if ( $status->isGood() ) { @@ -152,13 +150,14 @@ class ApiImageRotate extends ApiBase { /** * Checks that the user has permissions to perform rotations. - * @param $user User The user to check. + * @param User $user The user to check + * @param Title $title * @return string|null Permission error message, or null if there is no error */ protected function checkPermissions( $user, $title ) { $permissionErrors = array_merge( - $title->getUserPermissionsErrors( 'edit' , $user ), - $title->getUserPermissionsErrors( 'upload' , $user ) + $title->getUserPermissionsErrors( 'edit', $user ), + $title->getUserPermissionsErrors( 'upload', $user ) ); if ( $permissionErrors ) { @@ -179,7 +178,6 @@ class ApiImageRotate extends ApiBase { } public function getAllowedParams( $flags = 0 ) { - $pageSet = $this->getPageSet(); $result = array( 'rotation' => array( ApiBase::PARAM_TYPE => array( '90', '180', '270' ), @@ -198,7 +196,7 @@ class ApiImageRotate extends ApiBase { public function getParamDescription() { $pageSet = $this->getPageSet(); - return $pageSet->getParamDescription() + array( + return $pageSet->getFinalParamDescription() + array( 'rotation' => 'Degrees to rotate image clockwise', 'token' => 'Edit token. You can get one of these through action=tokens', ); @@ -220,7 +218,7 @@ class ApiImageRotate extends ApiBase { $pageSet = $this->getPageSet(); return array_merge( parent::getPossibleErrors(), - $pageSet->getPossibleErrors() + $pageSet->getFinalPossibleErrors() ); } diff --git a/includes/api/ApiImport.php b/includes/api/ApiImport.php index 1f0a5fab..f48a822e 100644 --- a/includes/api/ApiImport.php +++ b/includes/api/ApiImport.php @@ -57,7 +57,7 @@ class ApiImport extends ApiBase { $source = ImportStreamSource::newFromUpload( 'xml' ); } if ( !$source->isOK() ) { - $this->dieUsageMsg( $source->getErrorsArray() ); + $this->dieStatus( $source ); } $importer = new WikiImporter( $source->value ); @@ -66,8 +66,8 @@ class ApiImport extends ApiBase { } if ( isset( $params['rootpage'] ) ) { $statusRootPage = $importer->setTargetRootPage( $params['rootpage'] ); - if( !$statusRootPage->isGood() ) { - $this->dieUsageMsg( $statusRootPage->getErrorsArray() ); + if ( !$statusRootPage->isGood() ) { + $this->dieStatus( $statusRootPage ); } } $reporter = new ApiImportReporter( diff --git a/includes/api/ApiMain.php b/includes/api/ApiMain.php index 7b2fd914..c11f16cb 100644 --- a/includes/api/ApiMain.php +++ b/includes/api/ApiMain.php @@ -83,7 +83,7 @@ class ApiMain extends ApiBase { 'import' => 'ApiImport', 'userrights' => 'ApiUserrights', 'options' => 'ApiOptions', - 'imagerotate' =>'ApiImageRotate', + 'imagerotate' => 'ApiImageRotate', ); /** @@ -274,7 +274,7 @@ class ApiMain extends ApiBase { return; } - if ( !User::groupHasPermission( '*', 'read' ) ) { + if ( !User::isEveryoneAllowed( 'read' ) ) { // Private wiki, only private headers if ( $mode !== 'private' ) { wfDebug( __METHOD__ . ": ignoring request for $mode cache mode, private wiki\n" ); @@ -383,13 +383,8 @@ class ApiMain extends ApiBase { wfRunHooks( 'ApiMain::onException', array( $this, $e ) ); // Log it - if ( $e instanceof MWException && !( $e instanceof UsageException ) ) { - global $wgLogExceptionBacktrace; - if ( $wgLogExceptionBacktrace ) { - wfDebugLog( 'exception', $e->getLogMessage() . "\n" . $e->getTraceAsString() . "\n" ); - } else { - wfDebugLog( 'exception', $e->getLogMessage() ); - } + if ( !( $e instanceof UsageException ) ) { + MWExceptionHandler::logException( $e ); } // Handle any kind of exception by outputting properly formatted error message. @@ -418,7 +413,7 @@ class ApiMain extends ApiBase { } // Log the request whether or not there was an error - $this->logRequest( microtime( true ) - $t); + $this->logRequest( microtime( true ) - $t ); // Send cache headers after any code which might generate an error, to // avoid sending public cache headers for errors. @@ -609,7 +604,7 @@ class ApiMain extends ApiBase { $result = $this->getResult(); // Printer may not be initialized if the extractRequestParams() fails for the main module - if ( !isset ( $this->mPrinter ) ) { + if ( !isset( $this->mPrinter ) ) { // The printer has not been created yet. Try to manually get formatter value. $value = $this->getRequest()->getVal( 'format', self::API_DEFAULT_FORMAT ); if ( !$this->mModuleMgr->isDefined( $value, 'format' ) ) { @@ -763,7 +758,7 @@ class ApiMain extends ApiBase { */ protected function checkExecutePermissions( $module ) { $user = $this->getUser(); - if ( $module->isReadMode() && !User::groupHasPermission( '*', 'read' ) && + if ( $module->isReadMode() && !User::isEveryoneAllowed( 'read' ) && !$user->isAllowed( 'read' ) ) { $this->dieUsageMsg( 'readrequired' ); @@ -782,7 +777,7 @@ class ApiMain extends ApiBase { // Allow extensions to stop execution for arbitrary reasons. $message = false; - if( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { + if ( !wfRunHooks( 'ApiCheckCanExecute', array( $module, $user, &$message ) ) ) { $this->dieUsageMsg( $message ); } } @@ -857,7 +852,7 @@ class ApiMain extends ApiBase { ' ' . $request->getMethod() . ' ' . wfUrlencode( str_replace( ' ', '_', $this->getUser()->getName() ) ) . ' ' . $request->getIP() . - ' T=' . $milliseconds .'ms'; + ' T=' . $milliseconds . 'ms'; foreach ( $this->getParamsUsed() as $name ) { $value = $request->getVal( $name ); if ( $value === null ) { @@ -944,7 +939,7 @@ class ApiMain extends ApiBase { $unusedParams = array_diff( $allParams, $paramsUsed ); } - if( count( $unusedParams ) ) { + if ( count( $unusedParams ) ) { $s = count( $unusedParams ) > 1 ? 's' : ''; $this->setWarning( "Unrecognized parameter$s: '" . implode( $unusedParams, "', '" ) . "'" ); } @@ -957,7 +952,7 @@ class ApiMain extends ApiBase { */ protected function printResult( $isError ) { global $wgDebugAPI; - if( $wgDebugAPI !== false ) { + if ( $wgDebugAPI !== false ) { $this->setWarning( 'SECURITY WARNING: $wgDebugAPI is enabled' ); } @@ -1002,7 +997,7 @@ class ApiMain extends ApiBase { ApiBase::PARAM_DFLT => 'help', ApiBase::PARAM_TYPE => $this->mModuleMgr->getNames( 'action' ) ), - 'maxlag' => array( + 'maxlag' => array( ApiBase::PARAM_TYPE => 'integer' ), 'smaxage' => array( @@ -1014,7 +1009,7 @@ class ApiMain extends ApiBase { ApiBase::PARAM_DFLT => 0 ), 'requestid' => null, - 'servedby' => false, + 'servedby' => false, 'origin' => null, ); } @@ -1042,6 +1037,7 @@ class ApiMain extends ApiBase { 'servedby' => 'Include the hostname that served the request in the results. Unconditionally shown on error', 'origin' => array( 'When accessing the API using a cross-domain AJAX request (CORS), set this to the originating domain.', + 'This must be included in any pre-flight request, and therefore must be part of the request URI (not the POST body).', 'This must match one of the origins in the Origin: header exactly, so it has to be set to something like http://en.wikipedia.org or https://meta.wikimedia.org .', 'If this parameter does not match the Origin: header, a 403 response will be returned.', 'If this parameter matches the Origin: header and the origin is whitelisted, an Access-Control-Allow-Origin header will be set.', @@ -1114,7 +1110,7 @@ class ApiMain extends ApiBase { return array( 'API developers:', ' Roan Kattouw "<Firstname>.<Lastname>@gmail.com" (lead developer Sep 2007-2009)', - ' Victor Vasiliev - vasilvv at gee mail dot com', + ' Victor Vasiliev - vasilvv @ gmail . com', ' Bryan Tong Minh - bryan . tongminh @ gmail . com', ' Sam Reed - sam @ reedyboy . net', ' Yuri Astrakhan "<Firstname><Lastname>@gmail.com" (creator, lead developer Sep 2006-Sep 2007, 2012-present)', @@ -1143,7 +1139,7 @@ class ApiMain extends ApiBase { $this->setHelp(); // Get help text from cache if present $key = wfMemcKey( 'apihelp', $this->getModuleName(), - SpecialVersion::getVersion( 'nodb' ) ); + str_replace( ' ', '_', SpecialVersion::getVersion( 'nodb' ) ) ); if ( $wgAPICacheHelpTimeout > 0 ) { $cached = $wgMemc->get( $key ); if ( $cached ) { diff --git a/includes/api/ApiMove.php b/includes/api/ApiMove.php index 3e846e3b..c18036cf 100644 --- a/includes/api/ApiMove.php +++ b/includes/api/ApiMove.php @@ -88,7 +88,7 @@ class ApiMove extends ApiBase { $r['redirectcreated'] = ''; } - if( $toTitleExists ) { + if ( $toTitleExists ) { $r['moveoverredirect'] = ''; } @@ -99,7 +99,7 @@ class ApiMove extends ApiBase { if ( $retval === true ) { $r['talkfrom'] = $fromTalk->getPrefixedText(); $r['talkto'] = $toTalk->getPrefixedText(); - if( $toTalkExists ) { + if ( $toTalkExists ) { $r['talkmoveoverredirect'] = ''; } } else { diff --git a/includes/api/ApiOpenSearch.php b/includes/api/ApiOpenSearch.php index caf361ac..315ace37 100644 --- a/includes/api/ApiOpenSearch.php +++ b/includes/api/ApiOpenSearch.php @@ -1,7 +1,5 @@ <?php /** - * - * * Created on Oct 13, 2006 * * Copyright © 2006 Yuri Astrakhan "<Firstname><Lastname>@gmail.com" @@ -29,8 +27,20 @@ */ class ApiOpenSearch extends ApiBase { + /** + * Override built-in handling of format parameter. + * Only JSON is supported. + * + * @return ApiFormatBase + */ public function getCustomPrinter() { - return $this->getMain()->createPrinterByName( 'json' ); + $params = $this->extractRequestParams(); + $format = $params['format']; + $allowed = array( 'json', 'jsonfm' ); + if ( in_array( $format, $allowed ) ) { + return $this->getMain()->createPrinterByName( $format ); + } + return $this->getMain()->createPrinterByName( $allowed[0] ); } public function execute() { @@ -94,6 +104,10 @@ class ApiOpenSearch extends ApiBase { ApiBase::PARAM_ISMULTI => true ), 'suggest' => false, + 'format' => array( + ApiBase::PARAM_DFLT => 'json', + ApiBase::PARAM_TYPE => array( 'json', 'jsonfm' ), + ) ); } @@ -103,6 +117,7 @@ class ApiOpenSearch extends ApiBase { 'limit' => 'Maximum amount of results to return', 'namespace' => 'Namespaces to search', 'suggest' => 'Do nothing if $wgEnableOpenSearchSuggest is false', + 'format' => 'The format of the output', ); } diff --git a/includes/api/ApiOptions.php b/includes/api/ApiOptions.php index 8c996a26..7256066d 100644 --- a/includes/api/ApiOptions.php +++ b/includes/api/ApiOptions.php @@ -42,6 +42,10 @@ class ApiOptions extends ApiBase { $this->dieUsage( 'Anonymous users cannot change preferences', 'notloggedin' ); } + if ( !$user->isAllowed( 'editmyoptions' ) ) { + $this->dieUsage( 'You don\'t have permission to edit your options', 'permissiondenied' ); + } + $params = $this->extractRequestParams(); $changed = false; @@ -50,7 +54,7 @@ class ApiOptions extends ApiBase { } if ( $params['reset'] ) { - $user->resetOptions( $params['resetkinds'] ); + $user->resetOptions( $params['resetkinds'], $this->getContext() ); $changed = true; } @@ -83,7 +87,7 @@ class ApiOptions extends ApiBase { case 'registered-checkmatrix': // A key for a multiselect or checkmatrix option. $validation = true; - $value = $value !== null ? (bool) $value : null; + $value = $value !== null ? (bool)$value : null; break; case 'userjs': // Allow non-default preferences prefixed with 'userjs-', to be set by user scripts diff --git a/includes/api/ApiPageSet.php b/includes/api/ApiPageSet.php index 074efe4b..b05cb2b6 100644 --- a/includes/api/ApiPageSet.php +++ b/includes/api/ApiPageSet.php @@ -69,6 +69,9 @@ class ApiPageSet extends ApiBase { private $mFakePageId = -1; private $mCacheMode = 'public'; private $mRequestedPageFields = array(); + /** + * @var int + */ private $mDefaultNamespace = NS_MAIN; /** @@ -149,7 +152,6 @@ class ApiPageSet extends ApiBase { if ( !$isDryRun ) { $generator->executeGenerator( $this ); wfRunHooks( 'APIQueryGeneratorAfterExecute', array( &$generator, &$this ) ); - $this->resolvePendingRedirects(); } else { // Prevent warnings from being reported on these parameters $main = $this->getMain(); @@ -160,6 +162,10 @@ class ApiPageSet extends ApiBase { $generator->profileOut(); $this->profileIn(); + if ( !$isDryRun ) { + $this->resolvePendingRedirects(); + } + if ( !$isQuery ) { // If this pageset is not part of the query, we called profileIn() above $dbSource->profileOut(); @@ -185,7 +191,7 @@ class ApiPageSet extends ApiBase { if ( !$isDryRun ) { // Populate page information with the original user input - switch( $dataSource ) { + switch ( $dataSource ) { case 'titles': $this->initFromTitles( $this->mParams['titles'] ); break; @@ -404,7 +410,7 @@ class ApiPageSet extends ApiBase { * @return array of raw_prefixed_title (string) => prefixed_title (string) * @since 1.21 */ - public function getNormalizedTitlesAsResult( $result = null ) { + public function getNormalizedTitlesAsResult( $result = null ) { $values = array(); foreach ( $this->getNormalizedTitles() as $rawTitleStr => $titleStr ) { $values[] = array( @@ -595,13 +601,13 @@ class ApiPageSet extends ApiBase { } foreach ( $this->mRequestedPageFields as $fieldName => &$fieldValues ) { - $fieldValues[$pageId] = $row-> $fieldName; + $fieldValues[$pageId] = $row->$fieldName; } } /** * Do not use, does nothing, will be removed - * @deprecated 1.21 + * @deprecated since 1.21 */ public function finishPageSetGeneration() { wfDeprecated( __METHOD__, '1.21' ); @@ -859,7 +865,7 @@ class ApiPageSet extends ApiBase { $from = $this->mPendingRedirectIDs[$rdfrom]->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] ) ) { + if ( !$to->isExternal() && !isset( $this->mAllPages[$row->rd_namespace][$row->rd_title] ) ) { $lb->add( $row->rd_namespace, $row->rd_title ); } $this->mRedirectTitles[$from] = $to; diff --git a/includes/api/ApiParamInfo.php b/includes/api/ApiParamInfo.php index 27f8cefd..3e1a7531 100644 --- a/includes/api/ApiParamInfo.php +++ b/includes/api/ApiParamInfo.php @@ -149,7 +149,7 @@ class ApiParamInfo extends ApiBase { $item = array(); if ( is_numeric( $k ) ) { $retval['examples'] .= $v; - $result->setContent( $item, $v ); + ApiResult::setContent( $item, $v ); } else { if ( !is_array( $v ) ) { $item['description'] = $v; @@ -157,7 +157,7 @@ class ApiParamInfo extends ApiBase { $item['description'] = implode( $v, "\n" ); } $retval['examples'] .= $item['description'] . ' ' . $k; - $result->setContent( $item, $k ); + ApiResult::setContent( $item, $k ); } $retval['allexamples'][] = $item; } @@ -300,7 +300,7 @@ class ApiParamInfo extends ApiBase { } // Errors - $retval['errors'] = $this->parseErrors( $obj->getPossibleErrors() ); + $retval['errors'] = $this->parseErrors( $obj->getFinalPossibleErrors() ); $result->setIndexedTagName( $retval['errors'], 'error' ); return $retval; diff --git a/includes/api/ApiParse.php b/includes/api/ApiParse.php index 09b7a882..a369994b 100644 --- a/includes/api/ApiParse.php +++ b/includes/api/ApiParse.php @@ -44,6 +44,14 @@ class ApiParse extends ApiBase { $params = $this->extractRequestParams(); $text = $params['text']; $title = $params['title']; + if ( $title === null ) { + $titleProvided = false; + // A title is needed for parsing, so arbitrarily choose one + $title = 'API'; + } else { + $titleProvided = true; + } + $page = $params['page']; $pageid = $params['pageid']; $oldid = $params['oldid']; @@ -51,7 +59,7 @@ class ApiParse extends ApiBase { $model = $params['contentmodel']; $format = $params['contentformat']; - if ( !is_null( $page ) && ( !is_null( $text ) || $title != 'API' ) ) { + if ( !is_null( $page ) && ( !is_null( $text ) || $titleProvided ) ) { $this->dieUsage( 'The page parameter cannot be used together with the text and title parameters', 'params' ); } @@ -94,8 +102,7 @@ class ApiParse extends ApiBase { $titleObj = $rev->getTitle(); $wgTitle = $titleObj; $pageObj = WikiPage::factory( $titleObj ); - $popts = $pageObj->makeParserOptions( $this->getContext() ); - $popts->enableLimitReport( !$params['disablepp'] ); + $popts = $this->makeParserOptions( $pageObj, $params ); // If for some reason the "oldid" is actually the current revision, it may be cached if ( $rev->isCurrent() ) { @@ -152,8 +159,7 @@ class ApiParse extends ApiBase { $oldid = $pageObj->getLatest(); } - $popts = $pageObj->makeParserOptions( $this->getContext() ); - $popts->enableLimitReport( !$params['disablepp'] ); + $popts = $this->makeParserOptions( $pageObj, $params ); // Potentially cached $p_result = $this->getParsedContent( $pageObj, $popts, $pageid, @@ -170,11 +176,24 @@ class ApiParse extends ApiBase { $wgTitle = $titleObj; $pageObj = WikiPage::factory( $titleObj ); - $popts = $pageObj->makeParserOptions( $this->getContext() ); - $popts->enableLimitReport( !$params['disablepp'] ); + $popts = $this->makeParserOptions( $pageObj, $params ); if ( is_null( $text ) ) { - $this->dieUsage( 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?', 'params' ); + if ( $titleProvided && ( $prop || $params['generatexml'] ) ) { + $this->setWarning( + "'title' used without 'text', and parsed page properties were requested " . + "(did you mean to use 'page' instead of 'title'?)" + ); + } + // Prevent warning from ContentHandler::makeContent() + $text = ''; + } + + // If we are parsing text, do not use the content model of the default + // API title, but default to wikitext to keep BC. + if ( !$titleProvided && is_null( $model ) ) { + $model = CONTENT_MODEL_WIKITEXT; + $this->setWarning( "No 'title' or 'contentmodel' was given, assuming $model." ); } try { @@ -194,10 +213,10 @@ class ApiParse extends ApiBase { // Build a result and bail out $result_array = array(); $result_array['text'] = array(); - $result->setContent( $result_array['text'], $this->pstContent->serialize( $format ) ); + ApiResult::setContent( $result_array['text'], $this->pstContent->serialize( $format ) ); if ( isset( $prop['wikitext'] ) ) { $result_array['wikitext'] = array(); - $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) ); + ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) ); } $result->addValue( null, $this->getModuleName(), $result_array ); return; @@ -225,21 +244,35 @@ class ApiParse extends ApiBase { if ( isset( $prop['text'] ) ) { $result_array['text'] = array(); - $result->setContent( $result_array['text'], $p_result->getText() ); + ApiResult::setContent( $result_array['text'], $p_result->getText() ); } if ( !is_null( $params['summary'] ) ) { $result_array['parsedsummary'] = array(); - $result->setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) ); + ApiResult::setContent( $result_array['parsedsummary'], Linker::formatComment( $params['summary'], $titleObj ) ); + } + + if ( isset( $prop['langlinks'] ) || isset( $prop['languageshtml'] ) ) { + $langlinks = $p_result->getLanguageLinks(); + + if ( $params['effectivelanglinks'] ) { + // Link flags are ignored for now, but may in the future be + // included in the result. + $linkFlags = array(); + wfRunHooks( 'LanguageLinks', array( $titleObj, &$langlinks, &$linkFlags ) ); + } + } else { + $langlinks = false; } if ( isset( $prop['langlinks'] ) ) { - $result_array['langlinks'] = $this->formatLangLinks( $p_result->getLanguageLinks() ); + $result_array['langlinks'] = $this->formatLangLinks( $langlinks ); } if ( isset( $prop['languageshtml'] ) ) { - $languagesHtml = $this->languagesHtml( $p_result->getLanguageLinks() ); + $languagesHtml = $this->languagesHtml( $langlinks ); + $result_array['languageshtml'] = array(); - $result->setContent( $result_array['languageshtml'], $languagesHtml ); + ApiResult::setContent( $result_array['languageshtml'], $languagesHtml ); } if ( isset( $prop['categories'] ) ) { $result_array['categories'] = $this->formatCategoryLinks( $p_result->getCategories() ); @@ -247,7 +280,7 @@ class ApiParse extends ApiBase { if ( isset( $prop['categorieshtml'] ) ) { $categoriesHtml = $this->categoriesHtml( $p_result->getCategories() ); $result_array['categorieshtml'] = array(); - $result->setContent( $result_array['categorieshtml'], $categoriesHtml ); + ApiResult::setContent( $result_array['categorieshtml'], $categoriesHtml ); } if ( isset( $prop['links'] ) ) { $result_array['links'] = $this->formatLinks( $p_result->getLinks() ); @@ -288,7 +321,7 @@ class ApiParse extends ApiBase { if ( isset( $prop['headhtml'] ) ) { $result_array['headhtml'] = array(); - $result->setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) ); + ApiResult::setContent( $result_array['headhtml'], $context->getOutput()->headElement( $context->getSkin() ) ); } } @@ -298,10 +331,10 @@ class ApiParse extends ApiBase { if ( isset( $prop['wikitext'] ) ) { $result_array['wikitext'] = array(); - $result->setContent( $result_array['wikitext'], $this->content->serialize( $format ) ); + ApiResult::setContent( $result_array['wikitext'], $this->content->serialize( $format ) ); if ( !is_null( $this->pstContent ) ) { $result_array['psttext'] = array(); - $result->setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) ); + ApiResult::setContent( $result_array['psttext'], $this->pstContent->serialize( $format ) ); } } if ( isset( $prop['properties'] ) ) { @@ -321,7 +354,7 @@ class ApiParse extends ApiBase { $xml = $dom->__toString(); } $result_array['parsetree'] = array(); - $result->setContent( $result_array['parsetree'], $xml ); + ApiResult::setContent( $result_array['parsetree'], $xml ); } $result_mapping = array( @@ -346,6 +379,26 @@ class ApiParse extends ApiBase { } /** + * Constructs a ParserOptions object + * + * @param WikiPage $pageObj + * @param array $params + * + * @return ParserOptions + */ + protected function makeParserOptions( WikiPage $pageObj, array $params ) { + wfProfileIn( __METHOD__ ); + + $popts = $pageObj->makeParserOptions( $this->getContext() ); + $popts->enableLimitReport( !$params['disablepp'] ); + $popts->setIsPreview( $params['preview'] || $params['sectionpreview'] ); + $popts->setIsSectionPreview( $params['sectionpreview'] ); + + wfProfileOut( __METHOD__ ); + return $popts; + } + + /** * @param $page WikiPage * @param $popts ParserOptions * @param $pageId Int @@ -400,7 +453,7 @@ class ApiParse extends ApiBase { if ( $title ) { $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } - $this->getResult()->setContent( $entry, $bits[1] ); + ApiResult::setContent( $entry, $bits[1] ); $result[] = $entry; } return $result; @@ -411,7 +464,7 @@ class ApiParse extends ApiBase { foreach ( $links as $link => $sortkey ) { $entry = array(); $entry['sortkey'] = $sortkey; - $this->getResult()->setContent( $entry, $link ); + ApiResult::setContent( $entry, $link ); $result[] = $entry; } return $result; @@ -465,7 +518,7 @@ class ApiParse extends ApiBase { foreach ( $nslinks as $title => $id ) { $entry = array(); $entry['ns'] = $ns; - $this->getResult()->setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() ); + ApiResult::setContent( $entry, Title::makeTitle( $ns, $title )->getFullText() ); if ( $id != 0 ) { $entry['exists'] = ''; } @@ -487,7 +540,7 @@ class ApiParse extends ApiBase { $entry['url'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT ); } - $this->getResult()->setContent( $entry, $title->getFullText() ); + ApiResult::setContent( $entry, $title->getFullText() ); $result[] = $entry; } } @@ -499,7 +552,7 @@ class ApiParse extends ApiBase { foreach ( $headItems as $tag => $content ) { $entry = array(); $entry['tag'] = $tag; - $this->getResult()->setContent( $entry, $content ); + ApiResult::setContent( $entry, $content ); $result[] = $entry; } return $result; @@ -510,7 +563,7 @@ class ApiParse extends ApiBase { foreach ( $properties as $name => $value ) { $entry = array(); $entry['name'] = $name; - $this->getResult()->setContent( $entry, $value ); + ApiResult::setContent( $entry, $value ); $result[] = $entry; } return $result; @@ -521,7 +574,7 @@ class ApiParse extends ApiBase { foreach ( $css as $file => $link ) { $entry = array(); $entry['file'] = $file; - $this->getResult()->setContent( $entry, $link ); + ApiResult::setContent( $entry, $link ); $result[] = $entry; } return $result; @@ -537,9 +590,7 @@ class ApiParse extends ApiBase { public function getAllowedParams() { return array( - 'title' => array( - ApiBase::PARAM_DFLT => 'API', - ), + 'title' => null, 'text' => null, 'summary' => null, 'page' => null, @@ -575,10 +626,13 @@ class ApiParse extends ApiBase { ), 'pst' => false, 'onlypst' => false, + 'effectivelanglinks' => false, 'uselang' => null, 'section' => null, 'disablepp' => false, 'generatexml' => false, + 'preview' => false, + 'sectionpreview' => false, 'contentformat' => array( ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(), ), @@ -590,11 +644,13 @@ class ApiParse extends ApiBase { public function getParamDescription() { $p = $this->getModulePrefix(); + $wikitext = CONTENT_MODEL_WIKITEXT; return array( - 'text' => 'Wikitext to parse', + 'text' => "Text to parse. Use {$p}title or {$p}contentmodel to control the content model", 'summary' => 'Summary to parse', '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', + 'title' => "Title of page the text belongs to. " . + "If omitted, \"API\" is used as the title with content model $wikitext", '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", 'oldid' => "Parse the content of this revision. Overrides {$p}page and {$p}pageid", @@ -618,34 +674,52 @@ class ApiParse extends ApiBase { ' wikitext - Gives the original wikitext that was parsed', ' properties - Gives various properties defined in the parsed wikitext', ), + 'effectivelanglinks' => array( + 'Includes language links supplied by extensions', + '(for use with prop=langlinks|languageshtml)', + ), 'pst' => array( 'Do a pre-save transform on the input before parsing it', - 'Ignored if page, pageid or oldid is used' + "Only valid when used with {$p}text", ), 'onlypst' => array( 'Do a pre-save transform (PST) on the input, but don\'t parse it', - 'Returns the same wikitext, after a PST has been applied. Ignored if page, pageid or oldid is used' + 'Returns the same wikitext, after a PST has been applied.', + "Only valid when used with {$p}text", ), 'uselang' => 'Which language to parse the request in', 'section' => 'Only retrieve the content of this section number', 'disablepp' => 'Disable the PP Report from the parser output', - 'generatexml' => 'Generate XML parse tree (requires prop=wikitext)', - 'contentformat' => 'Content serialization format used for the input text', - 'contentmodel' => 'Content model of the new content', + 'generatexml' => "Generate XML parse tree (requires contentmodel=$wikitext)", + 'preview' => 'Parse in preview mode', + 'sectionpreview' => 'Parse in section preview mode (enables preview mode too)', + 'contentformat' => array( + 'Content serialization format used for the input text', + "Only valid when used with {$p}text", + ), + 'contentmodel' => array( + "Content model of the input text. Default is the model of the " . + "specified ${p}title, or $wikitext if ${p}title is not specified", + "Only valid when used with {$p}text", + ), ); } public function getDescription() { + $p = $this->getModulePrefix(); return array( - 'Parses wikitext and returns parser output', + 'Parses content and returns parser output', 'See the various prop-Modules of action=query to get information from the current version of a page', + 'There are several ways to specify the text to parse:', + "1) Specify a page or revision, using {$p}page, {$p}pageid, or {$p}oldid.", + "2) Specify content explicitly, using {$p}text, {$p}title, and {$p}contentmodel.", + "3) Specify only a summary to parse. {$p}prop should be given an empty value.", ); } public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'params', 'info' => 'The page parameter cannot be used together with the text and title parameters' ), - array( 'code' => 'params', 'info' => 'The text parameter should be passed with the title parameter. Should you be using the "page" parameter instead?' ), array( 'code' => 'missingrev', 'info' => 'There is no revision ID oldid' ), array( 'code' => 'permissiondenied', 'info' => 'You don\'t have permission to view deleted revisions' ), array( 'code' => 'missingtitle', 'info' => 'The page you specified doesn\'t exist' ), @@ -660,7 +734,10 @@ class ApiParse extends ApiBase { public function getExamples() { return array( - 'api.php?action=parse&text={{Project:Sandbox}}' + 'api.php?action=parse&page=Project:Sandbox' => 'Parse a page', + 'api.php?action=parse&text={{Project:Sandbox}}' => 'Parse wikitext', + 'api.php?action=parse&text={{PAGENAME}}&title=Test' => 'Parse wikitext, specifying the page title', + 'api.php?action=parse&summary=Some+[[link]]&prop=' => 'Parse a summary', ); } diff --git a/includes/api/ApiPatrol.php b/includes/api/ApiPatrol.php index 4d4fbba9..bd2fde2b 100644 --- a/includes/api/ApiPatrol.php +++ b/includes/api/ApiPatrol.php @@ -35,11 +35,27 @@ class ApiPatrol extends ApiBase { */ public function execute() { $params = $this->extractRequestParams(); - - $rc = RecentChange::newFromID( $params['rcid'] ); - if ( !$rc instanceof RecentChange ) { - $this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) ); + $this->requireOnlyOneParameter( $params, 'rcid', 'revid' ); + + if ( isset( $params['rcid'] ) ) { + $rc = RecentChange::newFromID( $params['rcid'] ); + if ( !$rc ) { + $this->dieUsageMsg( array( 'nosuchrcid', $params['rcid'] ) ); + } + } else { + $rev = Revision::newFromId( $params['revid'] ); + if ( !$rev ) { + $this->dieUsageMsg( array( 'nosuchrevid', $params['revid'] ) ); + } + $rc = $rev->getRecentChange(); + if ( !$rc ) { + $this->dieUsage( + 'The revision ' . $params['revid'] . " can't be patrolled as it's too old", + 'notpatrollable' + ); + } } + $retval = $rc->doMarkPatrolled( $this->getUser() ); if ( $retval ) { @@ -66,8 +82,10 @@ class ApiPatrol extends ApiBase { ApiBase::PARAM_REQUIRED => true ), 'rcid' => array( - ApiBase::PARAM_TYPE => 'integer', - ApiBase::PARAM_REQUIRED => true + ApiBase::PARAM_TYPE => 'integer' + ), + 'revid' => array( + ApiBase::PARAM_TYPE => 'integer' ), ); } @@ -76,6 +94,7 @@ class ApiPatrol extends ApiBase { return array( 'token' => 'Patrol token obtained from list=recentchanges', 'rcid' => 'Recentchanges ID to patrol', + 'revid' => 'Revision ID to patrol', ); } @@ -94,8 +113,16 @@ class ApiPatrol extends ApiBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'nosuchrcid', 'rcid' ), + return array_merge( + parent::getPossibleErrors(), + parent::getRequireOnlyOneParameterErrorMessages( array( 'rcid', 'revid' ) ), + array( + array( 'nosuchrcid', 'rcid' ), + array( 'nosuchrevid', 'revid' ), + array( + 'code' => 'notpatrollable', + 'info' => "The revision can't be patrolled as it's too old" + ) ) ); } @@ -109,7 +136,8 @@ class ApiPatrol extends ApiBase { public function getExamples() { return array( - 'api.php?action=patrol&token=123abc&rcid=230672766' + 'api.php?action=patrol&token=123abc&rcid=230672766', + 'api.php?action=patrol&token=123abc&revid=230672766' ); } diff --git a/includes/api/ApiProtect.php b/includes/api/ApiProtect.php index 503c6920..7830c8b4 100644 --- a/includes/api/ApiProtect.php +++ b/includes/api/ApiProtect.php @@ -103,8 +103,7 @@ class ApiProtect extends ApiBase { $status = $pageObj->doUpdateRestrictions( $protections, $expiryarray, $cascade, $params['reason'], $this->getUser() ); if ( !$status->isOK() ) { - $errors = $status->getErrorsArray(); - $this->dieUsageMsg( $errors[0] ); + $this->dieStatus( $status ); } $res = array( 'title' => $titleObj->getPrefixedText(), diff --git a/includes/api/ApiPurge.php b/includes/api/ApiPurge.php index 134f4a0d..0812ba51 100644 --- a/includes/api/ApiPurge.php +++ b/includes/api/ApiPurge.php @@ -42,15 +42,15 @@ class ApiPurge extends ApiBase { */ private static function addValues( array &$result, $values, $flag = null, $name = null ) { foreach ( $values as $val ) { - if( $val instanceof Title ) { + if ( $val instanceof Title ) { $v = array(); ApiQueryBase::addTitleInfo( $v, $val ); - } elseif( $name !== null ) { + } elseif ( $name !== null ) { $v = array( $name => $val ); } else { $v = $val; } - if( $flag !== null ) { + if ( $flag !== null ) { $v[$flag] = ''; } $result[] = $v; @@ -64,6 +64,7 @@ class ApiPurge extends ApiBase { $params = $this->extractRequestParams(); $forceLinkUpdate = $params['forcelinkupdate']; + $forceRecursiveLinkUpdate = $params['forcerecursivelinkupdate']; $pageSet = $this->getPageSet(); $pageSet->execute(); @@ -82,8 +83,8 @@ class ApiPurge extends ApiBase { $page->doPurge(); // Directly purge and skip the UI part of purge(). $r['purged'] = ''; - if ( $forceLinkUpdate ) { - if ( !$this->getUser()->pingLimiter() ) { + if ( $forceLinkUpdate || $forceRecursiveLinkUpdate ) { + if ( !$this->getUser()->pingLimiter( 'linkpurge' ) ) { global $wgEnableParserCache; $popts = $page->makeParserOptions( 'canonical' ); @@ -93,7 +94,8 @@ class ApiPurge extends ApiBase { $p_result = $content->getParserOutput( $title, $page->getLatest(), $popts, $wgEnableParserCache ); # Update the links tables - $updates = $content->getSecondaryDataUpdates( $title, null, true, $p_result ); + $updates = $content->getSecondaryDataUpdates( + $title, null, $forceRecursiveLinkUpdate, $p_result ); DataUpdate::runUpdates( $updates ); $r['linkupdate'] = ''; @@ -150,7 +152,10 @@ class ApiPurge extends ApiBase { } public function getAllowedParams( $flags = 0 ) { - $result = array( 'forcelinkupdate' => false ); + $result = array( + 'forcelinkupdate' => false, + 'forcerecursivelinkupdate' => false + ); if ( $flags ) { $result += $this->getPageSet()->getFinalParams( $flags ); } @@ -158,8 +163,12 @@ class ApiPurge extends ApiBase { } public function getParamDescription() { - return $this->getPageSet()->getParamDescription() - + array( 'forcelinkupdate' => 'Update the links tables' ); + return $this->getPageSet()->getFinalParamDescription() + + array( + 'forcelinkupdate' => 'Update the links tables', + 'forcerecursivelinkupdate' => 'Update the links table, and update ' . + 'the links tables for any page that uses this page as a template', + ); } public function getResultProperties() { @@ -204,7 +213,7 @@ class ApiPurge extends ApiBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), - $this->getPageSet()->getPossibleErrors() + $this->getPageSet()->getFinalPossibleErrors() ); } diff --git a/includes/api/ApiQuery.php b/includes/api/ApiQuery.php index f69ad234..e03837fc 100644 --- a/includes/api/ApiQuery.php +++ b/includes/api/ApiQuery.php @@ -64,6 +64,7 @@ class ApiQuery extends ApiBase { */ private static $QueryListModules = array( 'allcategories' => 'ApiQueryAllCategories', + 'allfileusages' => 'ApiQueryAllLinks', 'allimages' => 'ApiQueryAllImages', 'alllinks' => 'ApiQueryAllLinks', 'allpages' => 'ApiQueryAllPages', @@ -102,6 +103,7 @@ class ApiQuery extends ApiBase { 'allmessages' => 'ApiQueryAllMessages', 'siteinfo' => 'ApiQuerySiteinfo', 'userinfo' => 'ApiQueryUserInfo', + 'filerepoinfo' => 'ApiQueryFileRepoInfo', ); /** @@ -382,7 +384,6 @@ class ApiQuery extends ApiBase { $modules = $allModules; $tmp = $completeModules; $wasPosted = $this->getRequest()->wasPosted(); - $main = $this->getMain(); /** @var $module ApiQueryBase */ foreach ( $allModules as $moduleName => $module ) { @@ -513,7 +514,7 @@ class ApiQuery extends ApiBase { ApiQueryBase::addTitleInfo( $vals, $title ); $vals['special'] = ''; if ( $title->isSpecialPage() && - !SpecialPageFactory::exists( $title->getDbKey() ) ) { + !SpecialPageFactory::exists( $title->getDBkey() ) ) { $vals['missing'] = ''; } elseif ( $title->getNamespace() == NS_MEDIA && !wfFindFile( $title ) ) { @@ -697,7 +698,7 @@ class ApiQuery extends ApiBase { } public function getParamDescription() { - return $this->getPageSet()->getParamDescription() + array( + return $this->getPageSet()->getFinalParamDescription() + array( 'prop' => 'Which properties to get for the titles/revisions/pageids. Module help is available below', 'list' => 'Which lists to get. Module help is available below', 'meta' => 'Which metadata to get about the site. Module help is available below', @@ -723,7 +724,7 @@ class ApiQuery extends ApiBase { public function getPossibleErrors() { return array_merge( parent::getPossibleErrors(), - $this->getPageSet()->getPossibleErrors() + $this->getPageSet()->getFinalPossibleErrors() ); } @@ -736,6 +737,7 @@ class ApiQuery extends ApiBase { public function getHelpUrls() { return array( + 'https://www.mediawiki.org/wiki/API:Query', 'https://www.mediawiki.org/wiki/API:Meta', 'https://www.mediawiki.org/wiki/API:Properties', 'https://www.mediawiki.org/wiki/API:Lists', diff --git a/includes/api/ApiQueryAllCategories.php b/includes/api/ApiQueryAllCategories.php index 496a0eb8..3f5c6ee7 100644 --- a/includes/api/ApiQueryAllCategories.php +++ b/includes/api/ApiQueryAllCategories.php @@ -76,7 +76,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { if ( $dir == 'newer' ) { $this->addWhereRange( 'cat_pages', 'newer', $min, $max ); } else { - $this->addWhereRange( 'cat_pages', 'older', $max, $min); + $this->addWhereRange( 'cat_pages', 'older', $max, $min ); } if ( isset( $params['prefix'] ) ) { @@ -121,7 +121,7 @@ class ApiQueryAllCategories extends ApiQueryGeneratorBase { $pages[] = $titleObj; } else { $item = array(); - $result->setContent( $item, $titleObj->getText() ); + ApiResult::setContent( $item, $titleObj->getText() ); if ( isset( $prop['size'] ) ) { $item['size'] = intval( $row->cat_pages ); $item['pages'] = $row->cat_pages - $row->cat_subcats - $row->cat_files; diff --git a/includes/api/ApiQueryAllImages.php b/includes/api/ApiQueryAllImages.php index e24b162c..ccc7a3a2 100644 --- a/includes/api/ApiQueryAllImages.php +++ b/includes/api/ApiQueryAllImages.php @@ -189,7 +189,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { if ( !is_null( $params['mime'] ) ) { global $wgMiserMode; - if ( $wgMiserMode ) { + if ( $wgMiserMode ) { $this->dieUsage( 'MIME search disabled in Miser Mode', 'mimesearchdisabled' ); } @@ -260,7 +260,7 @@ class ApiQueryAllImages extends ApiQueryGeneratorBase { } public function getAllowedParams() { - return array ( + return array( 'sort' => array( ApiBase::PARAM_DFLT => 'name', ApiBase::PARAM_TYPE => array( diff --git a/includes/api/ApiQueryAllLinks.php b/includes/api/ApiQueryAllLinks.php index e355f8b0..47d1bcef 100644 --- a/includes/api/ApiQueryAllLinks.php +++ b/includes/api/ApiQueryAllLinks.php @@ -37,24 +37,41 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $prefix = 'al'; $this->table = 'pagelinks'; $this->tablePrefix = 'pl_'; + $this->fieldTitle = 'title'; $this->dfltNamespace = NS_MAIN; + $this->hasNamespace = true; $this->indexTag = 'l'; $this->description = 'Enumerate all links that point to a given namespace'; - $this->descriptionLink = 'link'; - $this->descriptionLinked = 'linked'; + $this->descriptionWhat = 'link'; + $this->descriptionTargets = 'linked titles'; $this->descriptionLinking = 'linking'; break; case 'alltransclusions': $prefix = 'at'; $this->table = 'templatelinks'; $this->tablePrefix = 'tl_'; + $this->fieldTitle = 'title'; $this->dfltNamespace = NS_TEMPLATE; + $this->hasNamespace = true; $this->indexTag = 't'; $this->description = 'List all transclusions (pages embedded using {{x}}), including non-existing'; - $this->descriptionLink = 'transclusion'; - $this->descriptionLinked = 'transcluded'; + $this->descriptionWhat = 'transclusion'; + $this->descriptionTargets = 'transcluded titles'; $this->descriptionLinking = 'transcluding'; break; + case 'allfileusages': + $prefix = 'af'; + $this->table = 'imagelinks'; + $this->tablePrefix = 'il_'; + $this->fieldTitle = 'to'; + $this->dfltNamespace = NS_FILE; + $this->hasNamespace = false; + $this->indexTag = 'f'; + $this->description = 'List all file usages, including non-existing'; + $this->descriptionWhat = 'file'; + $this->descriptionTargets = 'file titles'; + $this->descriptionLinking = 'using'; + break; default: ApiBase::dieDebug( __METHOD__, 'Unknown module name' ); } @@ -83,21 +100,29 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $params = $this->extractRequestParams(); $pfx = $this->tablePrefix; + $fieldTitle = $this->fieldTitle; $prop = array_flip( $params['prop'] ); $fld_ids = isset( $prop['ids'] ); $fld_title = isset( $prop['title'] ); + if ( $this->hasNamespace ) { + $namespace = $params['namespace']; + } else { + $namespace = $this->dfltNamespace; + } if ( $params['unique'] ) { if ( $fld_ids ) { $this->dieUsage( - "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionLink}s mode", + "{$this->getModuleName()} cannot return corresponding page ids in unique {$this->descriptionWhat}s mode", 'params' ); } $this->addOption( 'DISTINCT' ); } $this->addTables( $this->table ); - $this->addWhereFld( $pfx . 'namespace', $params['namespace'] ); + if ( $this->hasNamespace ) { + $this->addWhereFld( $pfx . 'namespace', $namespace ); + } $continue = !is_null( $params['continue'] ); if ( $continue ) { @@ -106,14 +131,14 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { if ( $params['unique'] ) { $this->dieContinueUsageIf( count( $continueArr ) != 1 ); $continueTitle = $db->addQuotes( $continueArr[0] ); - $this->addWhere( "{$pfx}title $op= $continueTitle" ); + $this->addWhere( "{$pfx}{$fieldTitle} $op= $continueTitle" ); } else { $this->dieContinueUsageIf( count( $continueArr ) != 2 ); $continueTitle = $db->addQuotes( $continueArr[0] ); $continueFrom = intval( $continueArr[1] ); $this->addWhere( - "{$pfx}title $op $continueTitle OR " . - "({$pfx}title = $continueTitle AND " . + "{$pfx}{$fieldTitle} $op $continueTitle OR " . + "({$pfx}{$fieldTitle} = $continueTitle AND " . "{$pfx}from $op= $continueFrom)" ); } @@ -122,22 +147,24 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { // 'continue' always overrides 'from' $from = ( $continue || is_null( $params['from'] ) ? null : $this->titlePartToKey( $params['from'] ) ); $to = ( is_null( $params['to'] ) ? null : $this->titlePartToKey( $params['to'] ) ); - $this->addWhereRange( $pfx . 'title', 'newer', $from, $to ); + $this->addWhereRange( $pfx . $fieldTitle, 'newer', $from, $to ); if ( isset( $params['prefix'] ) ) { - $this->addWhere( $pfx . 'title' . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); + $this->addWhere( $pfx . $fieldTitle . $db->buildLike( $this->titlePartToKey( $params['prefix'] ), $db->anyString() ) ); } - $this->addFields( array( 'pl_title' => $pfx . 'title' ) ); + $this->addFields( array( 'pl_title' => $pfx . $fieldTitle ) ); $this->addFieldsIf( array( 'pl_from' => $pfx . 'from' ), !$params['unique'] ); - $this->addOption( 'USE INDEX', $pfx . 'namespace' ); + if ( $this->hasNamespace ) { + $this->addOption( 'USE INDEX', $pfx . 'namespace' ); + } $limit = $params['limit']; $this->addOption( 'LIMIT', $limit + 1 ); $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' ); $orderBy = array(); - $orderBy[] = $pfx . 'title' . $sort; + $orderBy[] = $pfx . $fieldTitle . $sort; if ( !$params['unique'] ) { $orderBy[] = $pfx . 'from' . $sort; } @@ -166,7 +193,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { $vals['fromid'] = intval( $row->pl_from ); } if ( $fld_title ) { - $title = Title::makeTitle( $params['namespace'], $row->pl_title ); + $title = Title::makeTitle( $namespace, $row->pl_title ); ApiQueryBase::addTitleInfo( $vals, $title ); } $fit = $result->addValue( array( 'query', $this->getModuleName() ), null, $vals ); @@ -179,7 +206,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { break; } } elseif ( $params['unique'] ) { - $titles[] = Title::makeTitle( $params['namespace'], $row->pl_title ); + $titles[] = Title::makeTitle( $namespace, $row->pl_title ); } else { $pageids[] = $row->pl_from; } @@ -195,7 +222,7 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { } public function getAllowedParams() { - return array( + $allowedParams = array( 'continue' => null, 'from' => null, 'to' => null, @@ -228,30 +255,39 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { ) ), ); + if ( !$this->hasNamespace ) { + unset( $allowedParams['namespace'] ); + } + return $allowedParams; } public function getParamDescription() { $p = $this->getModulePrefix(); - $link = $this->descriptionLink; + $what = $this->descriptionWhat; + $targets = $this->descriptionTargets; $linking = $this->descriptionLinking; - return array( - 'from' => "The title of the $link to start enumerating from", - 'to' => "The title of the $link to stop enumerating at", - 'prefix' => "Search for all $link titles that begin with this value", + $paramDescription = array( + 'from' => "The title of the $what to start enumerating from", + 'to' => "The title of the $what to stop enumerating at", + 'prefix' => "Search for all $targets that begin with this value", 'unique' => array( - "Only show distinct $link titles. Cannot be used with {$p}prop=ids.", - 'When used as a generator, yields target pages instead of source pages.', + "Only show distinct $targets. Cannot be used with {$p}prop=ids.", + 'When used as a generator, yields target pages instead of source pages.', ), 'prop' => array( 'What pieces of information to include', " ids - Adds the pageid of the $linking page (Cannot be used with {$p}unique)", - " title - Adds the title of the $link", + " title - Adds the title of the $what", ), 'namespace' => 'The namespace to enumerate', - 'limit' => "How many total items to return", + 'limit' => 'How many total items to return', 'continue' => 'When more results are available, use this to continue', 'dir' => 'The direction in which to list', ); + if ( !$this->hasNamespace ) { + unset( $paramDescription['namespace'] ); + } + return $paramDescription; } public function getResultProperties() { @@ -272,29 +308,31 @@ class ApiQueryAllLinks extends ApiQueryGeneratorBase { public function getPossibleErrors() { $m = $this->getModuleName(); - $link = $this->descriptionLink; + $what = $this->descriptionWhat; return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$link}s mode" ), + array( 'code' => 'params', 'info' => "{$m} cannot return corresponding page ids in unique {$what}s mode" ), ) ); } public function getExamples() { $p = $this->getModulePrefix(); - $link = $this->descriptionLink; - $linked = $this->descriptionLinked; + $name = $this->getModuleName(); + $what = $this->descriptionWhat; + $targets = $this->descriptionTargets; return array( - "api.php?action=query&list=all{$link}s&{$p}from=B&{$p}prop=ids|title" - => "List $linked titles with page ids they are from, including missing ones. Start at B", - "api.php?action=query&list=all{$link}s&{$p}unique=&{$p}from=B" - => "List unique $linked titles", - "api.php?action=query&generator=all{$link}s&g{$p}unique=&g{$p}from=B" - => "Gets all $link targets, marking the missing ones", - "api.php?action=query&generator=all{$link}s&g{$p}from=B" - => "Gets pages containing the {$link}s", + "api.php?action=query&list={$name}&{$p}from=B&{$p}prop=ids|title" + => "List $targets with page ids they are from, including missing ones. Start at B", + "api.php?action=query&list={$name}&{$p}unique=&{$p}from=B" + => "List unique $targets", + "api.php?action=query&generator={$name}&g{$p}unique=&g{$p}from=B" + => "Gets all $targets, marking the missing ones", + "api.php?action=query&generator={$name}&g{$p}from=B" + => "Gets pages containing the {$what}s", ); } public function getHelpUrls() { - return "https://www.mediawiki.org/wiki/API:All{$this->descriptionLink}s"; + $name = ucfirst( $this->getModuleName() ); + return "https://www.mediawiki.org/wiki/API:{$name}"; } } diff --git a/includes/api/ApiQueryAllMessages.php b/includes/api/ApiQueryAllMessages.php index c9811b0d..d47c7b76 100644 --- a/includes/api/ApiQueryAllMessages.php +++ b/includes/api/ApiQueryAllMessages.php @@ -87,7 +87,7 @@ class ApiQueryAllMessages extends ApiQueryBase { foreach ( $messages_target as $message ) { // === 0: must be at beginning of string (position 0) if ( strpos( $message, $params['prefix'] ) === 0 ) { - if( !$skip ) { + if ( !$skip ) { $skip = true; } $messages_filtered[] = $message; diff --git a/includes/api/ApiQueryAllPages.php b/includes/api/ApiQueryAllPages.php index d718b967..d95980c2 100644 --- a/includes/api/ApiQueryAllPages.php +++ b/includes/api/ApiQueryAllPages.php @@ -174,7 +174,7 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase { $res = $this->select( __METHOD__ ); //Get gender information - if( MWNamespace::hasGenderDistinction( $params['namespace'] ) ) { + if ( MWNamespace::hasGenderDistinction( $params['namespace'] ) ) { $users = array(); foreach ( $res as $row ) { $users[] = $row->page_title; @@ -304,7 +304,10 @@ class ApiQueryAllPages extends ApiQueryGeneratorBase { 'prtype' => 'Limit to protected pages only', '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', + 'filterlanglinks' => array( + 'Filter based on whether a page has langlinks', + 'Note that this may not consider langlinks added by extensions.', + ), 'limit' => 'How many total pages to return.', 'prexpiry' => array( 'Which protection expiry to filter the page on', diff --git a/includes/api/ApiQueryAllUsers.php b/includes/api/ApiQueryAllUsers.php index 7283aa00..1948a51a 100644 --- a/includes/api/ApiQueryAllUsers.php +++ b/includes/api/ApiQueryAllUsers.php @@ -83,12 +83,12 @@ class ApiQueryAllUsers extends ApiQueryBase { if ( !is_null( $params['rights'] ) && count( $params['rights'] ) ) { $groups = array(); - foreach( $params['rights'] as $r ) { + foreach ( $params['rights'] as $r ) { $groups = array_merge( $groups, User::getGroupsWithPermission( $r ) ); } // no group with the given right(s) exists, no need for a query - if( !count( $groups ) ) { + if ( !count( $groups ) ) { $this->getResult()->setIndexedTagName_internal( array( 'query', $this->getModuleName() ), '' ); return; } diff --git a/includes/api/ApiQueryBacklinks.php b/includes/api/ApiQueryBacklinks.php index 3ef6b840..2d1089a7 100644 --- a/includes/api/ApiQueryBacklinks.php +++ b/includes/api/ApiQueryBacklinks.php @@ -229,10 +229,10 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { $orderBy = array(); $sort = ( $this->params['dir'] == 'descending' ? ' DESC' : '' ); // Don't order by namespace/title if it's constant in the WHERE clause - if( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) { + if ( $this->hasNS && count( array_unique( $allRedirNs ) ) != 1 ) { $orderBy[] = $this->bl_ns . $sort; } - if( count( array_unique( $allRedirDBkey ) ) != 1 ) { + if ( count( array_unique( $allRedirDBkey ) ) != 1 ) { $orderBy[] = $this->bl_title . $sort; } $orderBy[] = $this->bl_from . $sort; @@ -255,6 +255,9 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if ( $this->params['limit'] == 'max' ) { $this->params['limit'] = $this->getMain()->canApiHighLimits() ? $botMax : $userMax; $result->setParsedLimit( $this->getModuleName(), $this->params['limit'] ); + } else { + $this->params['limit'] = intval( $this->params['limit'] ); + $this->validateLimit( 'limit', $this->params['limit'], 1, $userMax, $botMax ); } $this->processContinue(); @@ -294,9 +297,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_FILE][$row-> { $this->bl_title } ]; + $parentID = $this->pageMap[NS_FILE][$row->{$this->bl_title}]; } $this->continueStr = $this->getContinueRedirStr( $parentID, $row->page_id ); break; @@ -377,8 +380,8 @@ class ApiQueryBacklinks extends ApiQueryGeneratorBase { if ( $row->page_is_redirect ) { $a['redirect'] = ''; } - $ns = $this->hasNS ? $row-> { $this->bl_ns } : NS_FILE; - $parentID = $this->pageMap[$ns][$row-> { $this->bl_title } ]; + $ns = $this->hasNS ? $row->{$this->bl_ns} : NS_FILE; + $parentID = $this->pageMap[$ns][$row->{$this->bl_title}]; // Put all the results in an array first $this->resultArr[$parentID]['redirlinks'][] = $a; $this->getResult()->setIndexedTagName( $this->resultArr[$parentID]['redirlinks'], $this->bl_code ); diff --git a/includes/api/ApiQueryBase.php b/includes/api/ApiQueryBase.php index 7819ead4..8668e04b 100644 --- a/includes/api/ApiQueryBase.php +++ b/includes/api/ApiQueryBase.php @@ -432,7 +432,7 @@ abstract class ApiQueryBase extends ApiBase { if ( trim( $key ) == '' ) { return ''; } - $t = Title::newFromDbKey( $key ); + $t = Title::newFromDBkey( $key ); // This really shouldn't happen but we gotta check anyway if ( !$t ) { $this->dieUsageMsg( array( 'invalidtitle', $key ) ); @@ -478,7 +478,7 @@ abstract class ApiQueryBase extends ApiBase { * @param $protocol String * @return null|string */ - public function prepareUrlQuerySearchString( $query = null, $protocol = null) { + public function prepareUrlQuerySearchString( $query = null, $protocol = null ) { $db = $this->getDb(); if ( !is_null( $query ) || $query != '' ) { if ( is_null( $protocol ) ) { diff --git a/includes/api/ApiQueryBlocks.php b/includes/api/ApiQueryBlocks.php index d9be9f28..e3c27f5e 100644 --- a/includes/api/ApiQueryBlocks.php +++ b/includes/api/ApiQueryBlocks.php @@ -63,7 +63,7 @@ class ApiQueryBlocks extends ApiQueryBase { $this->addTables( 'ipblocks' ); $this->addFields( 'ipb_auto' ); - $this->addFieldsIf ( 'ipb_id', $fld_id ); + $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 ); @@ -91,17 +91,30 @@ class ApiQueryBlocks extends ApiQueryBase { $this->addWhereFld( 'ipb_auto', 0 ); } if ( isset( $params['ip'] ) ) { - list( $ip, $range ) = IP::parseCIDR( $params['ip'] ); - if ( $ip && $range ) { - // We got a CIDR range - if ( $range < 16 ) - $this->dieUsage( 'CIDR ranges broader than /16 are not accepted', 'cidrtoobroad' ); - $lower = wfBaseConvert( $ip, 10, 16, 8, false ); - $upper = wfBaseConvert( $ip + pow( 2, 32 - $range ) - 1, 10, 16, 8, false ); + global $wgBlockCIDRLimit; + if ( IP::isIPv4( $params['ip'] ) ) { + $type = 'IPv4'; + $cidrLimit = $wgBlockCIDRLimit['IPv4']; + $prefixLen = 0; + } elseif ( IP::isIPv6( $params['ip'] ) ) { + $type = 'IPv6'; + $cidrLimit = $wgBlockCIDRLimit['IPv6']; + $prefixLen = 3; // IP::toHex output is prefixed with "v6-" } else { - $lower = $upper = IP::toHex( $params['ip'] ); + $this->dieUsage( 'IP parameter is not valid', 'param_ip' ); } - $prefix = substr( $lower, 0, 4 ); + + # Check range validity, if it's a CIDR + list( $ip, $range ) = IP::parseCIDR( $params['ip'] ); + if ( $ip !== false && $range !== false && $range < $cidrLimit ) { + $this->dieUsage( "$type CIDR ranges broader than /$cidrLimit are not accepted", 'cidrtoobroad' ); + } + + # Let IP::parseRange handle calculating $upper, instead of duplicating the logic here. + list( $lower, $upper ) = IP::parseRange( $params['ip'] ); + + # Extract the common prefix to any rangeblock affecting this IP/CIDR + $prefix = substr( $lower, 0, $prefixLen + floor( $cidrLimit / 4 ) ); # Fairly hard to make a malicious SQL statement out of hex characters, # but it is good practice to add quotes @@ -120,10 +133,10 @@ class ApiQueryBlocks extends ApiQueryBase { $show = array_flip( $params['show'] ); /* Check for conflicting parameters. */ - if ( ( isset ( $show['account'] ) && isset ( $show['!account'] ) ) - || ( isset ( $show['ip'] ) && isset ( $show['!ip'] ) ) - || ( isset ( $show['range'] ) && isset ( $show['!range'] ) ) - || ( isset ( $show['temp'] ) && isset ( $show['!temp'] ) ) + if ( ( isset( $show['account'] ) && isset( $show['!account'] ) ) + || ( isset( $show['ip'] ) && isset( $show['!ip'] ) ) + || ( isset( $show['range'] ) && isset( $show['!range'] ) ) + || ( isset( $show['temp'] ) && isset( $show['!temp'] ) ) ) { $this->dieUsageMsg( 'show' ); } @@ -294,6 +307,7 @@ class ApiQueryBlocks extends ApiQueryBase { } public function getParamDescription() { + global $wgBlockCIDRLimit; $p = $this->getModulePrefix(); return array( 'start' => 'The timestamp to start enumerating from', @@ -301,8 +315,12 @@ class ApiQueryBlocks extends ApiQueryBase { 'dir' => $this->getDirectionDescription( $p ), 'ids' => 'List of block IDs to list (optional)', 'users' => 'List of users to search for (optional)', - 'ip' => array( 'Get all blocks applying to this IP or CIDR range, including range blocks.', - 'Cannot be used together with bkusers. CIDR ranges broader than /16 are not accepted' ), + 'ip' => array( + 'Get all blocks applying to this IP or CIDR range, including range blocks.', + "Cannot be used together with bkusers. CIDR ranges broader than " . + "IPv4/{$wgBlockCIDRLimit['IPv4']} or IPv6/{$wgBlockCIDRLimit['IPv6']} " . + "are not accepted" + ), 'limit' => 'The maximum amount of blocks to list', 'prop' => array( 'Which properties to get', @@ -383,10 +401,19 @@ class ApiQueryBlocks extends ApiQueryBase { } public function getPossibleErrors() { + global $wgBlockCIDRLimit; return array_merge( parent::getPossibleErrors(), $this->getRequireOnlyOneParameterErrorMessages( array( 'users', 'ip' ) ), array( - array( 'code' => 'cidrtoobroad', 'info' => 'CIDR ranges broader than /16 are not accepted' ), + array( + 'code' => 'cidrtoobroad', + 'info' => "IPv4 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv4']} are not accepted" + ), + array( + 'code' => 'cidrtoobroad', + 'info' => "IPv6 CIDR ranges broader than /{$wgBlockCIDRLimit['IPv6']} are not accepted" + ), + array( 'code' => 'param_ip', 'info' => 'IP parameter is not valid' ), array( 'code' => 'param_user', 'info' => 'User parameter may not be empty' ), array( 'code' => 'param_user', 'info' => 'User name user is not valid' ), array( 'show' ), diff --git a/includes/api/ApiQueryCategories.php b/includes/api/ApiQueryCategories.php index 69a64415..5d714f57 100644 --- a/includes/api/ApiQueryCategories.php +++ b/includes/api/ApiQueryCategories.php @@ -49,7 +49,6 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { /** * @param $resultPageSet ApiPageSet - * @return */ private function run( $resultPageSet = null ) { if ( $this->getPageSet()->getGoodTitleCount() == 0 ) { @@ -174,7 +173,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { break; } - $titles[] = Title :: makeTitle( NS_CATEGORY, $row->cl_to ); + $titles[] = Title::makeTitle( NS_CATEGORY, $row->cl_to ); } $resultPageSet->populateFromTitles( $titles ); } @@ -184,7 +183,7 @@ class ApiQueryCategories extends ApiQueryGeneratorBase { return array( 'prop' => array( ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array ( + ApiBase::PARAM_TYPE => array( 'sortkey', 'timestamp', 'hidden', diff --git a/includes/api/ApiQueryCategoryMembers.php b/includes/api/ApiQueryCategoryMembers.php index 9dbd8593..704d108a 100644 --- a/includes/api/ApiQueryCategoryMembers.php +++ b/includes/api/ApiQueryCategoryMembers.php @@ -217,7 +217,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { if ( $fld_sortkeyprefix ) { $vals['sortkeyprefix'] = $row->cl_sortkey_prefix; } - if ( $fld_type ) { + if ( $fld_type ) { $vals['type'] = $row->cl_type; } if ( $fld_timestamp ) { @@ -258,7 +258,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'prop' => array( ApiBase::PARAM_DFLT => 'ids|title', ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array ( + ApiBase::PARAM_TYPE => array( 'ids', 'title', 'sortkey', @@ -267,7 +267,7 @@ class ApiQueryCategoryMembers extends ApiQueryGeneratorBase { 'timestamp', ) ), - 'namespace' => array ( + 'namespace' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => 'namespace', ), diff --git a/includes/api/ApiQueryDeletedrevs.php b/includes/api/ApiQueryDeletedrevs.php index 890e4ecf..82733133 100644 --- a/includes/api/ApiQueryDeletedrevs.php +++ b/includes/api/ApiQueryDeletedrevs.php @@ -50,7 +50,7 @@ class ApiQueryDeletedrevs extends ApiQueryBase { $fld_user = isset( $prop['user'] ); $fld_userid = isset( $prop['userid'] ); $fld_comment = isset( $prop['comment'] ); - $fld_parsedcomment = isset ( $prop['parsedcomment'] ); + $fld_parsedcomment = isset( $prop['parsedcomment'] ); $fld_minor = isset( $prop['minor'] ); $fld_len = isset( $prop['len'] ); $fld_sha1 = isset( $prop['sha1'] ); @@ -79,13 +79,13 @@ class ApiQueryDeletedrevs extends ApiQueryBase { 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', 'unique'*/ ) as $p ) { + foreach ( array( 'from', 'to', 'prefix', /*'namespace', '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 ) { + foreach ( array( 'start', 'end' ) as $p ) { if ( !is_null( $params[$p] ) ) { $this->dieUsage( "The {$p} parameter cannot be used in mode 3", 'badparams' ); } diff --git a/includes/api/ApiQueryDuplicateFiles.php b/includes/api/ApiQueryDuplicateFiles.php index 18dcba85..0311fa7f 100644 --- a/includes/api/ApiQueryDuplicateFiles.php +++ b/includes/api/ApiQueryDuplicateFiles.php @@ -48,8 +48,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { } /** - * @param $resultPageSet ApiPageSet - * @return + * @param ApiPageSet $resultPageSet */ private function run( $resultPageSet = null ) { $params = $this->extractRequestParams(); @@ -59,7 +58,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { } $images = $namespaces[NS_FILE]; - if( $params['dir'] == 'descending' ) { + if ( $params['dir'] == 'descending' ) { $images = array_reverse( $images ); } @@ -80,7 +79,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { } $filesToFind = array_keys( $images ); - if( $params['localonly'] ) { + if ( $params['localonly'] ) { $files = RepoGroup::singleton()->getLocalRepo()->findFiles( $filesToFind ); } else { $files = RepoGroup::singleton()->findFiles( $filesToFind ); @@ -98,29 +97,29 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { // find all files with the hashes, result format is: array( hash => array( dup1, dup2 ), hash1 => ... ) $filesToFindBySha1s = array_unique( array_values( $sha1s ) ); - if( $params['localonly'] ) { + if ( $params['localonly'] ) { $filesBySha1s = RepoGroup::singleton()->getLocalRepo()->findBySha1s( $filesToFindBySha1s ); } else { $filesBySha1s = RepoGroup::singleton()->findBySha1s( $filesToFindBySha1s ); } // iterate over $images to handle continue param correct - foreach( $images as $image => $pageId ) { - if( !isset( $sha1s[$image] ) ) { + foreach ( $images as $image => $pageId ) { + if ( !isset( $sha1s[$image] ) ) { continue; //file does not exist } $sha1 = $sha1s[$image]; $dupFiles = $filesBySha1s[$sha1]; - if( $params['dir'] == 'descending' ) { + if ( $params['dir'] == 'descending' ) { $dupFiles = array_reverse( $dupFiles ); } /** @var $dupFile File */ foreach ( $dupFiles as $dupFile ) { $dupName = $dupFile->getName(); - if( $image == $dupName && $dupFile->isLocal() ) { + if ( $image == $dupName && $dupFile->isLocal() ) { continue; //ignore the local file itself } - if( $skipUntilThisDup !== false && $dupName < $skipUntilThisDup ) { + if ( $skipUntilThisDup !== false && $dupName < $skipUntilThisDup ) { continue; //skip to pos after the image from continue param } $skipUntilThisDup = false; @@ -139,7 +138,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { 'user' => $dupFile->getUser( 'text' ), 'timestamp' => wfTimestamp( TS_ISO_8601, $dupFile->getTimestamp() ) ); - if( !$dupFile->isLocal() ) { + if ( !$dupFile->isLocal() ) { $r['shared'] = ''; } $fit = $this->addPageSubItem( $pageId, $r ); @@ -149,7 +148,7 @@ class ApiQueryDuplicateFiles extends ApiQueryGeneratorBase { } } } - if( !$fit ) { + if ( !$fit ) { break; } } diff --git a/includes/api/ApiQueryExtLinksUsage.php b/includes/api/ApiQueryExtLinksUsage.php index eb9cdf9e..456e87ba 100644 --- a/includes/api/ApiQueryExtLinksUsage.php +++ b/includes/api/ApiQueryExtLinksUsage.php @@ -123,7 +123,7 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { if ( $fld_url ) { $to = $row->el_to; // expand protocol-relative urls - if( $params['expandurl'] ) { + if ( $params['expandurl'] ) { $to = wfExpandUrl( $to, PROTO_CANONICAL ); } $vals['url'] = $to; @@ -218,13 +218,13 @@ class ApiQueryExtLinksUsage extends ApiQueryGeneratorBase { ), 'offset' => 'Used for paging. Use the value returned for "continue"', 'protocol' => array( - "Protocol of the url. If empty and {$p}query set, the protocol is http.", + "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. See [[Special:LinkSearch]]. Leave empty to list all external links', 'namespace' => 'The page namespace(s) to enumerate.', 'limit' => 'How many pages to return.', - 'expandurl' => 'Expand protocol-relative urls with the canonical protocol', + 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol', ); if ( $wgMiserMode ) { diff --git a/includes/api/ApiQueryExternalLinks.php b/includes/api/ApiQueryExternalLinks.php index 761b49ea..583ef697 100644 --- a/includes/api/ApiQueryExternalLinks.php +++ b/includes/api/ApiQueryExternalLinks.php @@ -88,7 +88,7 @@ class ApiQueryExternalLinks extends ApiQueryBase { $entry = array(); $to = $row->el_to; // expand protocol-relative urls - if( $params['expandurl'] ) { + if ( $params['expandurl'] ) { $to = wfExpandUrl( $to, PROTO_CANONICAL ); } ApiResult::setContent( $entry, $to ); @@ -131,11 +131,11 @@ class ApiQueryExternalLinks extends ApiQueryBase { '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.", + "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', - 'expandurl' => 'Expand protocol-relative urls with the canonical protocol', + 'expandurl' => 'Expand protocol-relative URLs with the canonical protocol', ); } @@ -148,7 +148,7 @@ class ApiQueryExternalLinks extends ApiQueryBase { } public function getDescription() { - return 'Returns all external urls (not interwikis) from the given page(s)'; + return 'Returns all external URLs (not interwikis) from the given page(s)'; } public function getPossibleErrors() { diff --git a/includes/api/ApiQueryFileRepoInfo.php b/includes/api/ApiQueryFileRepoInfo.php new file mode 100644 index 00000000..3a353533 --- /dev/null +++ b/includes/api/ApiQueryFileRepoInfo.php @@ -0,0 +1,115 @@ +<?php +/** + * Copyright © 2013 Mark Holmquist <mtraceur@member.fsf.org> + * + * 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 + * @since 1.22 + */ + +/** + * A query action to return meta information about the foreign file repos + * configured on the wiki. + * + * @ingroup API + */ +class ApiQueryFileRepoInfo extends ApiQueryBase { + + public function __construct( $query, $moduleName ) { + parent::__construct( $query, $moduleName, 'fri' ); + } + + protected function getInitialisedRepoGroup() { + $repoGroup = RepoGroup::singleton(); + + if ( !$repoGroup->reposInitialised ) { + $repoGroup->initialiseRepos(); + } + + return $repoGroup; + } + + public function execute() { + $params = $this->extractRequestParams(); + $props = array_flip( $params['prop'] ); + + $repos = array(); + + $repoGroup = $this->getInitialisedRepoGroup(); + + $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$repos, $props ) { + $repos[] = array_intersect_key( $repo->getInfo(), $props ); + } ); + + $repos[] = array_intersect_key( $repoGroup->localRepo->getInfo(), $props ); + + $result = $this->getResult(); + $result->setIndexedTagName( $repos, 'repo' ); + $result->addValue( array( 'query' ), 'repos', $repos ); + } + + public function getCacheMode( $params ) { + return 'public'; + } + + public function getAllowedParams() { + $props = $this->getProps(); + + return array( + 'prop' => array( + ApiBase::PARAM_DFLT => join( '|', $props ), + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => $props, + ), + ); + } + + public function getProps() { + $props = array(); + $repoGroup = $this->getInitialisedRepoGroup(); + + $repoGroup->forEachForeignRepo( function ( $repo ) use ( &$props ) { + $props = array_merge( $props, array_keys( $repo->getInfo() ) ); + } ); + + return array_values( array_unique( array_merge( $props, array_keys( $repoGroup->localRepo->getInfo() ) ) ) ); + } + + public function getParamDescription() { + $p = $this->getModulePrefix(); + return array( + 'prop' => array( + 'Which repository properties to get (there may be more available on some wikis):', + ' apiurl - URL to the repository API - helpful for getting image info from the host.', + ' name - The key of the repository - used in e.g. $wgForeignFileRepos and imageinfo return values.', + ' displayname - The human-readable name of the repository wiki.', + ' rooturl - Root URL for image paths.', + ' local - Whether that repository is the local one or not.', + ), + ); + } + + public function getDescription() { + return 'Return meta information about image repositories configured on the wiki.'; + } + + public function getExamples() { + return array( + 'api.php?action=query&meta=filerepoinfo&friprop=apiurl|name|displayname', + ); + } +} diff --git a/includes/api/ApiQueryFilearchive.php b/includes/api/ApiQueryFilearchive.php index 021074a9..f53cd386 100644 --- a/includes/api/ApiQueryFilearchive.php +++ b/includes/api/ApiQueryFilearchive.php @@ -218,7 +218,7 @@ class ApiQueryFilearchive extends ApiQueryBase { } public function getAllowedParams() { - return array ( + return array( 'from' => null, 'continue' => null, 'to' => null, @@ -281,7 +281,7 @@ class ApiQueryFilearchive extends ApiQueryBase { ' parseddescription - Parse the description on the version', ' mime - Adds MIME of the image', ' mediatype - Adds the media type of the image', - ' metadata - Lists EXIF metadata for the version of the image', + ' metadata - Lists Exif metadata for the version of the image', ' bitdepth - Adds the bit depth of the version', ' archivename - Adds the file name of the archive version for non-latest versions' ), @@ -373,4 +373,8 @@ class ApiQueryFilearchive extends ApiQueryBase { ), ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Filearchive'; + } } diff --git a/includes/api/ApiQueryIWBacklinks.php b/includes/api/ApiQueryIWBacklinks.php index b47d31f2..ebae3e76 100644 --- a/includes/api/ApiQueryIWBacklinks.php +++ b/includes/api/ApiQueryIWBacklinks.php @@ -239,4 +239,8 @@ class ApiQueryIWBacklinks extends ApiQueryGeneratorBase { 'api.php?action=query&generator=iwbacklinks&giwbltitle=Test&giwblprefix=wikibooks&prop=info' ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Iwbacklinks'; + } } diff --git a/includes/api/ApiQueryIWLinks.php b/includes/api/ApiQueryIWLinks.php index fc77b4e6..be539311 100644 --- a/includes/api/ApiQueryIWLinks.php +++ b/includes/api/ApiQueryIWLinks.php @@ -81,8 +81,8 @@ class ApiQueryIWLinks extends ApiQueryBase { $this->addOption( 'ORDER BY', 'iwl_from' . $sort ); } else { $this->addOption( 'ORDER BY', array( - 'iwl_title' . $sort, - 'iwl_from' . $sort + 'iwl_from' . $sort, + 'iwl_title' . $sort )); } } else { @@ -90,9 +90,10 @@ class ApiQueryIWLinks extends ApiQueryBase { if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { $this->addOption( 'ORDER BY', 'iwl_prefix' . $sort ); } else { - $this->addOption( 'ORDER BY', array ( + $this->addOption( 'ORDER BY', array( 'iwl_from' . $sort, - 'iwl_prefix' . $sort + 'iwl_prefix' . $sort, + 'iwl_title' . $sort )); } } @@ -192,4 +193,8 @@ class ApiQueryIWLinks extends ApiQueryBase { 'api.php?action=query&prop=iwlinks&titles=Main%20Page' => 'Get interwiki links from the [[Main Page]]', ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Iwlinks'; + } } diff --git a/includes/api/ApiQueryImageInfo.php b/includes/api/ApiQueryImageInfo.php index 95c2745a..0ea28684 100644 --- a/includes/api/ApiQueryImageInfo.php +++ b/includes/api/ApiQueryImageInfo.php @@ -72,7 +72,7 @@ class ApiQueryImageInfo extends ApiQueryBase { $result = $this->getResult(); //search only inside the local repo - if( $params['localonly'] ) { + if ( $params['localonly'] ) { $images = RepoGroup::singleton()->getLocalRepo()->findFiles( $titles ); } else { $images = RepoGroup::singleton()->findFiles( $titles ); @@ -82,12 +82,17 @@ class ApiQueryImageInfo extends ApiQueryBase { $start = $title === $fromTitle ? $fromTimestamp : $params['start']; if ( !isset( $images[$title] ) ) { - $result->addValue( - array( 'query', 'pages', intval( $pageId ) ), - 'imagerepository', '' - ); - // The above can't fail because it doesn't increase the result size - continue; + if ( isset( $prop['uploadwarning'] ) ) { + // Uploadwarning needs info about non-existing files + $images[$title] = wfLocalFile( $title ); + } else { + $result->addValue( + array( 'query', 'pages', intval( $pageId ) ), + 'imagerepository', '' + ); + // The above can't fail because it doesn't increase the result size + continue; + } } /** @var $img File */ @@ -170,9 +175,12 @@ class ApiQueryImageInfo extends ApiQueryBase { } break; } - $fit = $this->addPageSubItem( $pageId, - self::getInfo( $oldie, $prop, $result, - $finalThumbParams, $params['metadataversion'] ) ); + $fit = self::getTransformCount() < self::TRANSFORM_LIMIT && + $this->addPageSubItem( $pageId, + self::getInfo( $oldie, $prop, $result, + $finalThumbParams, $params['metadataversion'] + ) + ); if ( !$fit ) { if ( count( $pageIds[NS_FILE] ) == 1 ) { $this->setContinueEnumParameter( 'start', @@ -199,21 +207,20 @@ class ApiQueryImageInfo extends ApiQueryBase { 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" ); - } - if ( $params['urlwidth'] != -1 ) { $scale = array(); $scale['width'] = $params['urlwidth']; $scale['height'] = $params['urlheight']; + } elseif ( $params['urlheight'] != -1 ) { + // Height is specified but width isn't + // Don't set $scale['width']; this signals mergeThumbParams() to fill it with the image's width + $scale = array(); + $scale['height'] = $params['urlheight']; } else { $scale = null; if ( $params['urlparam'] ) { $this->dieUsage( "{$p}urlparam requires {$p}urlwidth", "urlparam_no_width" ); } - return $scale; } return $scale; @@ -228,7 +235,21 @@ class ApiQueryImageInfo extends ApiQueryBase { * @param string $otherParams of otherParams (iiurlparam). * @return Array of parameters for transform. */ - protected function mergeThumbParams ( $image, $thumbParams, $otherParams ) { + protected function mergeThumbParams( $image, $thumbParams, $otherParams ) { + global $wgThumbLimits; + + if ( !isset( $thumbParams['width'] ) && isset( $thumbParams['height'] ) ) { + // We want to limit only by height in this situation, so pass the + // image's full width as the limiting width. But some file types + // don't have a width of their own, so pick something arbitrary so + // thumbnailing the default icon works. + if ( $image->getWidth() <= 0 ) { + $thumbParams['width'] = max( $wgThumbLimits ); + } else { + $thumbParams['width'] = $image->getWidth(); + } + } + if ( !$otherParams ) { return $thumbParams; } @@ -254,8 +275,8 @@ class ApiQueryImageInfo extends ApiQueryBase { 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" ); + $this->setWarning( "Ignoring width value set in {$p}urlparam ({$paramList['width']}) " + . "in favor of width value derived from {$p}urlwidth/{$p}urlheight ({$thumbParams['width']})" ); } } @@ -342,6 +363,7 @@ class ApiQueryImageInfo extends ApiQueryBase { $mediatype = isset( $prop['mediatype'] ); $archive = isset( $prop['archivename'] ); $bitdepth = isset( $prop['bitdepth'] ); + $uploadwarning = isset( $prop['uploadwarning'] ); if ( ( $url || $sha1 || $meta || $mime || $mediatype || $archive || $bitdepth ) && $file->isDeleted( File::DELETED_FILE ) ) { @@ -411,6 +433,10 @@ class ApiQueryImageInfo extends ApiQueryBase { $vals['bitdepth'] = $file->getBitDepth(); } + if ( $uploadwarning ) { + $vals['html'] = SpecialUpload::getExistsWarning( UploadBase::getExistsWarning( $file ) ); + } + return $vals; } @@ -461,7 +487,7 @@ class ApiQueryImageInfo extends ApiQueryBase { if ( $start === null ) { $start = $img->getTimestamp(); } - return $img->getOriginalTitle()->getText() . '|' . $start; + return $img->getOriginalTitle()->getDBkey() . '|' . $start; } public function getAllowedParams() { @@ -537,9 +563,10 @@ class ApiQueryImageInfo extends ApiQueryBase { 'thumbmime' => ' thumbmime - Adds MIME type of the image thumbnail' . ' (requires url and param ' . $modulePrefix . 'urlwidth)', 'mediatype' => ' mediatype - Adds the media type of the image', - 'metadata' => ' metadata - Lists EXIF metadata for the version 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', + 'uploadwarning' => ' uploadwarning - Used by the Special:Upload page to get information about an existing file. Not intended for use outside MediaWiki core', ); } @@ -547,7 +574,7 @@ class ApiQueryImageInfo extends ApiQueryBase { * Returns the descriptions for the properties provided by getPropertyNames() * * @param array $filter List of properties to filter out - * + * @param string $modulePrefix * @return array */ public static function getPropertyDescriptions( $filter = array(), $modulePrefix = '' ) { @@ -566,11 +593,12 @@ class ApiQueryImageInfo extends ApiQueryBase { return array( 'prop' => self::getPropertyDescriptions( array(), $p ), '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", + 'For performance reasons if this option is used, ' . + 'no more than ' . self::TRANSFORM_LIMIT . ' scaled images will be returned.' ), + 'urlheight' => "Similar to {$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', + 'limit' => 'How many image revisions to return per image', '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.", @@ -714,8 +742,6 @@ class ApiQueryImageInfo extends ApiQueryBase { 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 doesn't " . - "match the one in {$p}urlwidth" ), ) ); } diff --git a/includes/api/ApiQueryInfo.php b/includes/api/ApiQueryInfo.php index 37cd9159..017684ed 100644 --- a/includes/api/ApiQueryInfo.php +++ b/includes/api/ApiQueryInfo.php @@ -56,11 +56,11 @@ class ApiQueryInfo extends ApiQueryBase { * @return void */ public function requestExtraData( $pageSet ) { - global $wgDisableCounters; + global $wgDisableCounters, $wgContentHandlerUseDB; $pageSet->requestField( 'page_restrictions' ); // when resolving redirects, no page will have this field - if( !$pageSet->isResolvingRedirects() ) { + if ( !$pageSet->isResolvingRedirects() ) { $pageSet->requestField( 'page_is_redirect' ); } $pageSet->requestField( 'page_is_new' ); @@ -70,6 +70,9 @@ class ApiQueryInfo extends ApiQueryBase { $pageSet->requestField( 'page_touched' ); $pageSet->requestField( 'page_latest' ); $pageSet->requestField( 'page_len' ); + if ( $wgContentHandlerUseDB ) { + $pageSet->requestField( 'page_content_model' ); + } } /** @@ -348,6 +351,10 @@ class ApiQueryInfo extends ApiQueryBase { $titleExists = $pageid > 0; //$title->exists() needs pageid, which is not set for all title objects $ns = $title->getNamespace(); $dbkey = $title->getDBkey(); + + $pageInfo['contentmodel'] = $title->getContentModel(); + $pageInfo['pagelanguage'] = $title->getPageLanguage()->getCode(); + if ( $titleExists ) { global $wgDisableCounters; @@ -476,7 +483,7 @@ class ApiQueryInfo extends ApiQueryBase { $this->protections[$title->getNamespace()][$title->getDBkey()][] = $a; } // Also check old restrictions - foreach( $this->titles as $pageId => $title ) { + foreach ( $this->titles as $pageId => $title ) { if ( $this->pageRestrictions[$pageId] ) { $namespace = $title->getNamespace(); $dbKey = $title->getDBkey(); @@ -662,7 +669,9 @@ class ApiQueryInfo extends ApiQueryBase { private function getWatchedInfo() { $user = $this->getUser(); - if ( $user->isAnon() || count( $this->everything ) == 0 ) { + if ( $user->isAnon() || count( $this->everything ) == 0 + || !$user->isAllowed( 'viewmywatchlist' ) + ) { return; } @@ -819,7 +828,8 @@ class ApiQueryInfo extends ApiQueryBase { 'starttimestamp' => array( ApiBase::PROP_TYPE => 'timestamp', ApiBase::PROP_NULLABLE => true - ) + ), + 'contentmodel' => 'string', ), 'watched' => array( 'watched' => 'boolean' diff --git a/includes/api/ApiQueryLangBacklinks.php b/includes/api/ApiQueryLangBacklinks.php index 7a4880a4..5bd451b6 100644 --- a/includes/api/ApiQueryLangBacklinks.php +++ b/includes/api/ApiQueryLangBacklinks.php @@ -195,7 +195,7 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase { 'prop' => array( 'Which properties to get', ' lllang - Adds the language code of the language link', - ' lltitle - Adds the title of the language ink', + ' lltitle - Adds the title of the language link', ), 'limit' => 'How many total pages to return', 'dir' => 'The direction in which to list', @@ -223,7 +223,8 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase { 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"', + 'Using neither parameter is effectively "All Language Links".', + 'Note that this may not consider language links added by extensions.', ); } @@ -239,4 +240,8 @@ class ApiQueryLangBacklinks extends ApiQueryGeneratorBase { 'api.php?action=query&generator=langbacklinks&glbltitle=Test&glbllang=fr&prop=info' ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Langbacklinks'; + } } diff --git a/includes/api/ApiQueryLangLinks.php b/includes/api/ApiQueryLangLinks.php index ac65d2d2..aa796e31 100644 --- a/includes/api/ApiQueryLangLinks.php +++ b/includes/api/ApiQueryLangLinks.php @@ -67,18 +67,19 @@ class ApiQueryLangLinks extends ApiQueryBase { ); } - $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' ); - if ( isset( $params['lang'] ) ) { + //FIXME: (follow-up) To allow extensions to add to the language links, we need + // to load them all, add the extra links, then apply paging. + // Should not be terrible, it's not going to be more than a few hundred links. + + // Note that, since (ll_from, ll_lang) is a unique key, we don't need + // to sort by ll_title to ensure deterministic ordering. + $sort = ( $params['dir'] == 'descending' ? ' DESC' : '' ); + 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' . $sort ); - } else { - $this->addOption( 'ORDER BY', array( - 'll_title' . $sort, - 'll_from' . $sort - )); } + $this->addOption( 'ORDER BY', 'll_from' . $sort ); } else { // Don't order by ll_from if it's constant in the WHERE clause if ( count( $this->getPageSet()->getGoodTitles() ) == 1 ) { diff --git a/includes/api/ApiQueryLogEvents.php b/includes/api/ApiQueryLogEvents.php index 73dcea49..1a2719ed 100644 --- a/includes/api/ApiQueryLogEvents.php +++ b/includes/api/ApiQueryLogEvents.php @@ -49,12 +49,12 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->fld_ids = isset( $prop['ids'] ); $this->fld_title = isset( $prop['title'] ); $this->fld_type = isset( $prop['type'] ); - $this->fld_action = isset ( $prop['action'] ); + $this->fld_action = isset( $prop['action'] ); $this->fld_user = isset( $prop['user'] ); $this->fld_userid = isset( $prop['userid'] ); $this->fld_timestamp = isset( $prop['timestamp'] ); $this->fld_comment = isset( $prop['comment'] ); - $this->fld_parsedcomment = isset ( $prop['parsedcomment'] ); + $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); $this->fld_details = isset( $prop['details'] ); $this->fld_tags = isset( $prop['tags'] ); @@ -67,7 +67,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addTables( array( 'logging', 'user', 'page' ) ); $this->addOption( 'STRAIGHT_JOIN' ); $this->addJoinConds( array( - 'user' => array( 'JOIN', + 'user' => array( 'LEFT JOIN', 'user_id=log_user' ), 'page' => array( 'LEFT JOIN', array( 'log_namespace=page_namespace', @@ -82,8 +82,8 @@ class ApiQueryLogEvents extends ApiQueryBase { ) ); $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( array( 'log_user', 'log_user_text', 'user_name' ), $this->fld_user ); + $this->addFieldsIf( 'log_user', $this->fld_userid ); $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 ); @@ -98,8 +98,7 @@ class ApiQueryLogEvents extends ApiQueryBase { $this->addTables( 'change_tag' ); $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'log_id=ct_log_id' ) ) ) ); $this->addWhereFld( 'ct_tag', $params['tag'] ); - global $wgOldChangeTagsIndex; - $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; + $index['change_tag'] = 'change_tag_tag_id'; } if ( !is_null( $params['action'] ) ) { @@ -241,7 +240,7 @@ class ApiQueryLogEvents extends ApiQueryBase { break; case 'rights': $vals2 = array(); - if( $legacy ) { + if ( $legacy ) { list( $vals2['old'], $vals2['new'] ) = $params; } else { $vals2['new'] = implode( ', ', $params['5::newgroups'] ); @@ -265,6 +264,11 @@ class ApiQueryLogEvents extends ApiQueryBase { $vals[$type] = $vals2; $params = null; break; + case 'upload': + if ( isset( $params['img_timestamp'] ) ) { + $params['img_timestamp'] = wfTimestamp( TS_ISO_8601, $params['img_timestamp'] ); + } + break; } if ( !is_null( $params ) ) { $logParams = array(); @@ -331,10 +335,10 @@ class ApiQueryLogEvents extends ApiQueryBase { $vals['userhidden'] = ''; } else { if ( $this->fld_user ) { - $vals['user'] = $row->user_name; + $vals['user'] = $row->user_name === null ? $row->log_user_text : $row->user_name; } if ( $this->fld_userid ) { - $vals['userid'] = $row->user_id; + $vals['userid'] = $row->log_user; } if ( !$row->log_user ) { diff --git a/includes/api/ApiQueryORM.php b/includes/api/ApiQueryORM.php index 41d8f11c..a23ff06b 100644 --- a/includes/api/ApiQueryORM.php +++ b/includes/api/ApiQueryORM.php @@ -228,7 +228,7 @@ abstract class ApiQueryORM extends ApiQueryBase { * @return array */ public function getAllowedParams() { - $params = array ( + $params = array( 'props' => array( ApiBase::PARAM_TYPE => $this->getTable()->getFieldNames(), ApiBase::PARAM_ISMULTI => true, @@ -252,7 +252,7 @@ abstract class ApiQueryORM extends ApiQueryBase { * @return array */ public function getParamDescription() { - $descriptions = array ( + $descriptions = array( 'props' => 'Fields to query', 'continue' => 'Offset number from where to continue the query', 'limit' => 'Max amount of rows to return', diff --git a/includes/api/ApiQueryPagesWithProp.php b/includes/api/ApiQueryPagesWithProp.php index 0132fc3e..6f2f02e4 100644 --- a/includes/api/ApiQueryPagesWithProp.php +++ b/includes/api/ApiQueryPagesWithProp.php @@ -133,7 +133,7 @@ class ApiQueryPagesWithProp extends ApiQueryGeneratorBase { 'prop' => array( ApiBase::PARAM_DFLT => 'ids|title', ApiBase::PARAM_ISMULTI => true, - ApiBase::PARAM_TYPE => array ( + ApiBase::PARAM_TYPE => array( 'ids', 'title', 'value', diff --git a/includes/api/ApiQueryProtectedTitles.php b/includes/api/ApiQueryProtectedTitles.php index 4aa00007..222ad074 100644 --- a/includes/api/ApiQueryProtectedTitles.php +++ b/includes/api/ApiQueryProtectedTitles.php @@ -157,7 +157,7 @@ class ApiQueryProtectedTitles extends ApiQueryGeneratorBase { ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => array_diff( $wgRestrictionLevels, array( '' ) ) ), - 'limit' => array ( + 'limit' => array( ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', ApiBase::PARAM_MIN => 1, diff --git a/includes/api/ApiQueryQueryPage.php b/includes/api/ApiQueryQueryPage.php index b03bdfb8..79fe0498 100644 --- a/includes/api/ApiQueryQueryPage.php +++ b/includes/api/ApiQueryQueryPage.php @@ -32,27 +32,18 @@ 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" ); + require_once "$IP/includes/QueryPage.php"; // Build mapping from special page names to QueryPage classes - global $wgQueryPages; + global $wgQueryPages, $wgAPIUselessQueryPages; $this->qpMap = array(); foreach ( $wgQueryPages as $page ) { - if( !in_array( $page[1], $this->uselessQueryPages ) ) { + if ( !in_array( $page[1], $wgAPIUselessQueryPages ) ) { $this->qpMap[$page[1]] = $page[0]; } } @@ -222,4 +213,8 @@ class ApiQueryQueryPage extends ApiQueryGeneratorBase { 'api.php?action=query&list=querypage&qppage=Ancientpages' ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Querypage'; + } } diff --git a/includes/api/ApiQueryRandom.php b/includes/api/ApiQueryRandom.php index ae3bb893..2754bdae 100644 --- a/includes/api/ApiQueryRandom.php +++ b/includes/api/ApiQueryRandom.php @@ -185,4 +185,8 @@ class ApiQueryRandom extends ApiQueryGeneratorBase { public function getExamples() { return 'api.php?action=query&list=random&rnnamespace=0&rnlimit=2'; } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Random'; + } } diff --git a/includes/api/ApiQueryRecentChanges.php b/includes/api/ApiQueryRecentChanges.php index 8aceab22..6b10bdc6 100644 --- a/includes/api/ApiQueryRecentChanges.php +++ b/includes/api/ApiQueryRecentChanges.php @@ -39,7 +39,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false, $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false, $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false, - $fld_tags = false, $token = array(); + $fld_tags = false, $fld_sha1 = false, $token = array(); private $tokenFunctions; @@ -121,6 +121,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { $this->fld_patrolled = isset( $prop['patrolled'] ); $this->fld_loginfo = isset( $prop['loginfo'] ); $this->fld_tags = isset( $prop['tags'] ); + $this->fld_sha1 = isset( $prop['sha1'] ); } public function execute() { @@ -273,6 +274,12 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { $this->addFields( 'ts_tags' ); } + if ( $this->fld_sha1 ) { + $this->addTables( 'revision' ); + $this->addJoinConds( array( 'revision' => array( 'LEFT JOIN', array( 'rc_this_oldid=rev_id' ) ) ) ); + $this->addFields( array( 'rev_sha1', 'rev_deleted' ) ); + } + if ( $params['toponly'] || $showRedirects ) { $this->addTables( 'page' ); $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', array( 'rc_namespace=page_namespace', 'rc_title=page_title' ) ) ) ); @@ -287,8 +294,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { $this->addTables( 'change_tag' ); $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rc_id=ct_rc_id' ) ) ) ); $this->addWhereFld( 'ct_tag', $params['tag'] ); - global $wgOldChangeTagsIndex; - $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; + $index['change_tag'] = 'change_tag_tag_id'; } $this->token = $params['token']; @@ -475,6 +481,19 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { } } + if ( $this->fld_sha1 && $row->rev_sha1 !== null ) { + // The RevDel check should currently never pass due to the + // rc_deleted = 0 condition in the WHERE clause, but in case that + // ever changes we check it here too. + if ( $row->rev_deleted & Revision::DELETED_TEXT ) { + $vals['sha1hidden'] = ''; + } elseif ( $row->rev_sha1 !== '' ) { + $vals['sha1'] = wfBaseConvert( $row->rev_sha1, 36, 16, 40 ); + } else { + $vals['sha1'] = ''; + } + } + if ( !is_null( $this->token ) ) { $tokenFunctions = $this->getTokenFunctions(); foreach ( $this->token as $t ) { @@ -499,7 +518,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { } return $retval; } - switch( $type ) { + switch ( $type ) { case 'edit': return RC_EDIT; case 'new': @@ -571,7 +590,8 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { 'redirect', 'patrolled', 'loginfo', - 'tags' + 'tags', + 'sha1', ) ), 'token' => array( @@ -638,6 +658,7 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { ' patrolled - Tags edits that have been patrolled', ' loginfo - Adds log information (logid, logtype, etc) to log entries', ' tags - Lists tags for the entry', + ' sha1 - Adds the content checksum for entries associated with a revision', ), 'token' => 'Which tokens to obtain for each change', 'show' => array( @@ -735,7 +756,17 @@ class ApiQueryRecentChanges extends ApiQueryGeneratorBase { ApiBase::PROP_TYPE => 'string', ApiBase::PROP_NULLABLE => true ) - ) + ), + 'sha1' => array( + 'sha1' => array( + ApiBase::PROP_TYPE => 'string', + ApiBase::PROP_NULLABLE => true + ), + 'sha1hidden' => array( + ApiBase::PROP_TYPE => 'boolean', + ApiBase::PROP_NULLABLE => true + ), + ), ); self::addTokenProperties( $props, $this->getTokenFunctions() ); diff --git a/includes/api/ApiQueryRevisions.php b/includes/api/ApiQueryRevisions.php index 192fe873..415288ef 100644 --- a/includes/api/ApiQueryRevisions.php +++ b/includes/api/ApiQueryRevisions.php @@ -146,17 +146,17 @@ class ApiQueryRevisions extends ApiQueryBase { $prop = array_flip( $params['prop'] ); // Optional fields - $this->fld_ids = isset ( $prop['ids'] ); + $this->fld_ids = isset( $prop['ids'] ); // $this->addFieldsIf('rev_text_id', $this->fld_ids); // should this be exposed? - $this->fld_flags = isset ( $prop['flags'] ); - $this->fld_timestamp = isset ( $prop['timestamp'] ); - $this->fld_comment = isset ( $prop['comment'] ); - $this->fld_parsedcomment = isset ( $prop['parsedcomment'] ); - $this->fld_size = isset ( $prop['size'] ); - $this->fld_sha1 = isset ( $prop['sha1'] ); - $this->fld_contentmodel = isset ( $prop['contentmodel'] ); + $this->fld_flags = isset( $prop['flags'] ); + $this->fld_timestamp = isset( $prop['timestamp'] ); + $this->fld_comment = isset( $prop['comment'] ); + $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); + $this->fld_size = isset( $prop['size'] ); + $this->fld_sha1 = isset( $prop['sha1'] ); + $this->fld_contentmodel = isset( $prop['contentmodel'] ); $this->fld_userid = isset( $prop['userid'] ); - $this->fld_user = isset ( $prop['user'] ); + $this->fld_user = isset( $prop['user'] ); $this->token = $params['token']; if ( !empty( $params['contentformat'] ) ) { @@ -189,8 +189,7 @@ class ApiQueryRevisions extends ApiQueryBase { $this->addTables( 'change_tag' ); $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) ); $this->addWhereFld( 'ct_tag', $params['tag'] ); - global $wgOldChangeTagsIndex; - $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; + $index['change_tag'] = 'change_tag_tag_id'; } if ( isset( $prop['content'] ) || !is_null( $this->difftotext ) ) { @@ -520,7 +519,7 @@ class ApiQueryRevisions extends ApiQueryBase { } else { $this->setWarning( "Conversion to XML is supported for wikitext only, " . $title->getPrefixedDBkey() . - " uses content model " . $content->getModel() . ")" ); + " uses content model " . $content->getModel() ); } } @@ -533,7 +532,7 @@ class ApiQueryRevisions extends ApiQueryBase { } else { $this->setWarning( "Template expansion is supported for wikitext only, " . $title->getPrefixedDBkey() . - " uses content model " . $content->getModel() . ")" ); + " uses content model " . $content->getModel() ); $text = false; } @@ -550,7 +549,7 @@ class ApiQueryRevisions extends ApiQueryBase { if ( !$content->isSupportedFormat( $format ) ) { $name = $title->getPrefixedDBkey(); - $this->dieUsage( "The requested format {$this->contentFormat} is not supported ". + $this->dieUsage( "The requested format {$this->contentFormat} is not supported " . "for content model $model used by $name", 'badformat' ); } @@ -593,7 +592,7 @@ class ApiQueryRevisions extends ApiQueryBase { $name = $title->getPrefixedDBkey(); - $this->dieUsage( "The requested format {$this->contentFormat} is not supported for ". + $this->dieUsage( "The requested format {$this->contentFormat} is not supported for " . "content model $model used by $name", 'badformat' ); } diff --git a/includes/api/ApiQuerySearch.php b/includes/api/ApiQuerySearch.php index 86183391..36b55979 100644 --- a/includes/api/ApiQuerySearch.php +++ b/includes/api/ApiQuerySearch.php @@ -31,6 +31,14 @@ */ class ApiQuerySearch extends ApiQueryGeneratorBase { + /** + * When $wgSearchType is null, $wgSearchAlternatives[0] is null. Null isn't + * a valid option for an array for PARAM_TYPE, so we'll use a fake name + * that can't possibly be a class name and describes what the null behavior + * does + */ + const BACKEND_NULL_PARAM = 'database-backed'; + public function __construct( $query, $moduleName ) { parent::__construct( $query, $moduleName, 'sr' ); } @@ -59,7 +67,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { $prop = array_flip( $params['prop'] ); // Create search engine instance and set options - $search = SearchEngine::create(); + $search = isset( $params['backend'] ) && $params['backend'] != self::BACKEND_NULL_PARAM ? + SearchEngine::create( $params['backend'] ) : SearchEngine::create(); $search->setLimitOffset( $limit + 1, $params['offset'] ); $search->setNamespaces( $params['namespace'] ); $search->showRedirects = $params['redirects']; @@ -93,6 +102,8 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } if ( is_null( $matches ) ) { $this->dieUsage( "{$what} search is disabled", "search-{$what}-disabled" ); + } elseif ( $matches instanceof Status && !$matches->isGood() ) { + $this->dieUsage( $matches->getWikiText(), 'search-error' ); } $apiResult = $this->getResult(); @@ -199,7 +210,9 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { } public function getAllowedParams() { - return array( + global $wgSearchType; + + $params = array( 'search' => array( ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true @@ -252,10 +265,23 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { ApiBase::PARAM_MAX2 => ApiBase::LIMIT_SML2 ) ); + + $alternatives = SearchEngine::getSearchTypes(); + if ( count( $alternatives ) > 1 ) { + if ( $alternatives[0] === null ) { + $alternatives[0] = self::BACKEND_NULL_PARAM; + } + $params['backend'] = array( + ApiBase::PARAM_DFLT => $wgSearchType, + ApiBase::PARAM_TYPE => $alternatives, + ); + } + + return $params; } public function getParamDescription() { - return array( + $descriptions = array( 'search' => 'Search for all page titles (or content) that has this value', 'namespace' => 'The namespace(s) to enumerate', 'what' => 'Search inside the text or titles', @@ -278,6 +304,12 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { 'offset' => 'Use this value to continue paging (return by query)', 'limit' => 'How many total pages to return' ); + + if ( count( SearchEngine::getSearchTypes() ) > 1 ) { + $descriptions['backend'] = 'Which search backend to use, if not the default'; + } + + return $descriptions; } public function getResultProperties() { @@ -345,6 +377,7 @@ class ApiQuerySearch extends ApiQueryGeneratorBase { return array_merge( parent::getPossibleErrors(), array( array( 'code' => 'search-text-disabled', 'info' => 'text search is disabled' ), array( 'code' => 'search-title-disabled', 'info' => 'title search is disabled' ), + array( 'code' => 'search-error', 'info' => 'search error has occurred' ), ) ); } diff --git a/includes/api/ApiQuerySiteinfo.php b/includes/api/ApiQuerySiteinfo.php index 810e1d6b..a7767062 100644 --- a/includes/api/ApiQuerySiteinfo.php +++ b/includes/api/ApiQuerySiteinfo.php @@ -121,14 +121,29 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data = array(); $mainPage = Title::newMainPage(); $data['mainpage'] = $mainPage->getPrefixedText(); - $data['base'] = wfExpandUrl( $mainPage->getFullUrl(), PROTO_CURRENT ); + $data['base'] = wfExpandUrl( $mainPage->getFullURL(), PROTO_CURRENT ); $data['sitename'] = $GLOBALS['wgSitename']; + $data['logo'] = $GLOBALS['wgLogo']; $data['generator'] = "MediaWiki {$GLOBALS['wgVersion']}"; $data['phpversion'] = phpversion(); $data['phpsapi'] = PHP_SAPI; $data['dbtype'] = $GLOBALS['wgDBtype']; $data['dbversion'] = $this->getDB()->getServerVersion(); + $allowFrom = array( '' ); + $allowException = true; + if ( !$GLOBALS['wgAllowExternalImages'] ) { + if ( $GLOBALS['wgEnableImageWhitelist'] ) { + $data['imagewhitelistenabled'] = ''; + } + $allowFrom = $GLOBALS['wgAllowExternalImagesFrom']; + $allowException = !empty( $allowFrom ); + } + if ( $allowException ) { + $data['externalimages'] = (array)$allowFrom; + $this->getResult()->setIndexedTagName( $data['externalimages'], 'prefix' ); + } + if ( !$wgDisableLangConversion ) { $data['langconversion'] = ''; } @@ -170,15 +185,15 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['lang'] = $GLOBALS['wgLanguageCode']; $fallbacks = array(); - foreach( $wgContLang->getFallbackLanguages() as $code ) { + foreach ( $wgContLang->getFallbackLanguages() as $code ) { $fallbacks[] = array( 'code' => $code ); } $data['fallback'] = $fallbacks; $this->getResult()->setIndexedTagName( $data['fallback'], 'lang' ); - if( $wgContLang->hasVariants() ) { + if ( $wgContLang->hasVariants() ) { $variants = array(); - foreach( $wgContLang->getVariants() as $code ) { + foreach ( $wgContLang->getVariants() as $code ) { $variants[] = array( 'code' => $code ); } $data['variants'] = $variants; @@ -281,6 +296,8 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data[] = $item; } + sort( $data ); + $this->getResult()->setIndexedTagName( $data, 'ns' ); return $this->getResult()->addValue( 'query', $property, $data ); } @@ -345,10 +362,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { $val['language'] = $langNames[$prefix]; } $val['url'] = wfExpandUrl( $row['iw_url'], PROTO_CURRENT ); - if( isset( $row['iw_wikiid'] ) ) { + if ( isset( $row['iw_wikiid'] ) ) { $val['wikiid'] = $row['iw_wikiid']; } - if( isset( $row['iw_api'] ) ) { + if ( isset( $row['iw_api'] ) ) { $val['api'] = $row['iw_api']; } @@ -404,11 +421,15 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data['activeusers'] = intval( SiteStats::activeUsers() ); $data['admins'] = intval( SiteStats::numberingroup( 'sysop' ) ); $data['jobs'] = intval( SiteStats::jobs() ); + + wfRunHooks( 'APIQuerySiteInfoStatisticsInfo', array( &$data ) ); + return $this->getResult()->addValue( 'query', $property, $data ); } protected function appendUserGroups( $property, $numberInGroup ) { - global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups, $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; + global $wgGroupPermissions, $wgAddGroups, $wgRemoveGroups; + global $wgGroupsAddToSelf, $wgGroupsRemoveFromSelf; $data = array(); $result = $this->getResult(); @@ -456,7 +477,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { global $wgFileExtensions; $data = array(); - foreach ( $wgFileExtensions as $ext ) { + foreach ( array_unique( $wgFileExtensions ) as $ext ) { $data[] = array( 'ext' => $ext ); } $this->getResult()->setIndexedTagName( $data, 'fe' ); @@ -520,7 +541,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { $data = array( 'url' => $url ? $url : '', - 'text' => $text ? $text : '' + 'text' => $text ? $text : '' ); return $this->getResult()->addValue( 'query', $property, $data ); @@ -544,9 +565,17 @@ class ApiQuerySiteinfo extends ApiQueryBase { public function appendSkins( $property ) { $data = array(); + $usable = Skin::getUsableSkins(); + $default = Skin::normalizeKey( 'default' ); foreach ( Skin::getSkinNames() as $name => $displayName ) { $skin = array( 'code' => $name ); ApiResult::setContent( $skin, $displayName ); + if ( !isset( $usable[$name] ) ) { + $skin['unusable'] = ''; + } + if ( $name === $default ) { + $skin['default'] = ''; + } $data[] = $skin; } $this->getResult()->setIndexedTagName( $data, 'skin' ); @@ -661,13 +690,15 @@ class ApiQuerySiteinfo extends ApiQueryBase { ' specialpagealiases - List of special page aliases', ' magicwords - List of magic words and their aliases', ' statistics - Returns site statistics', - " interwikimap - Returns interwiki map (optionally filtered, (optionally localised by using {$p}inlanguagecode))", + " interwikimap - Returns interwiki map " . + "(optionally filtered, (optionally localised by using {$p}inlanguagecode))", ' dbrepllag - Returns database server with the highest replication lag', ' usergroups - Returns user groups and the associated permissions', ' extensions - Returns extensions installed on the wiki', ' 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 (optionally localised by using {$p}inlanguagecode)", + " languages - Returns a list of languages MediaWiki supports" . + "(optionally localised by using {$p}inlanguagecode)", ' skins - Returns a list of all enabled skins', ' extensiontags - Returns a list of parser extension tags', ' functionhooks - Returns a list of parser function hooks', @@ -675,7 +706,7 @@ class ApiQuerySiteinfo extends ApiQueryBase { ' variables - Returns a list of variable IDs', ' protocols - Returns a list of protocols that are allowed in external links.', ), - 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', + 'filteriw' => 'Return only local or only nonlocal entries of the interwiki map', 'showalldb' => 'List all database servers, not just the one lagging the most', 'numberingroup' => 'Lists the number of users in user groups', 'inlanguagecode' => 'Language code for localised language names (best effort, use CLDR extension)', @@ -687,9 +718,10 @@ class ApiQuerySiteinfo extends ApiQueryBase { } public function getPossibleErrors() { - return array_merge( parent::getPossibleErrors(), array( - array( 'code' => 'includeAllDenied', 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' ), - ) ); + return array_merge( parent::getPossibleErrors(), array( array( + 'code' => 'includeAllDenied', + 'info' => 'Cannot view all servers info unless $wgShowHostnames is true' + ), ) ); } public function getExamples() { diff --git a/includes/api/ApiQueryTags.php b/includes/api/ApiQueryTags.php index e0637ff7..732df9a4 100644 --- a/includes/api/ApiQueryTags.php +++ b/includes/api/ApiQueryTags.php @@ -133,8 +133,7 @@ class ApiQueryTags extends ApiQueryBase { public function getAllowedParams() { return array( - 'continue' => array( - ), + 'continue' => null, 'limit' => array( ApiBase::PARAM_DFLT => 10, ApiBase::PARAM_TYPE => 'limit', @@ -195,4 +194,8 @@ class ApiQueryTags extends ApiQueryBase { 'api.php?action=query&list=tags&tgprop=displayname|description|hitcount' ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Tags'; + } } diff --git a/includes/api/ApiQueryUserContributions.php b/includes/api/ApiQueryUserContributions.php index 597c412d..9a9be7b2 100644 --- a/includes/api/ApiQueryUserContributions.php +++ b/includes/api/ApiQueryUserContributions.php @@ -48,7 +48,7 @@ class ApiQueryContributions extends ApiQueryBase { $this->fld_ids = isset( $prop['ids'] ); $this->fld_title = isset( $prop['title'] ); $this->fld_comment = isset( $prop['comment'] ); - $this->fld_parsedcomment = isset ( $prop['parsedcomment'] ); + $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); $this->fld_size = isset( $prop['size'] ); $this->fld_sizediff = isset( $prop['sizediff'] ); $this->fld_flags = isset( $prop['flags'] ); @@ -83,10 +83,10 @@ class ApiQueryContributions extends ApiQueryBase { // Do the actual query. $res = $this->select( __METHOD__ ); - if( $this->fld_sizediff ) { + if ( $this->fld_sizediff ) { $revIds = array(); foreach ( $res as $row ) { - if( $row->rev_parent_id ) { + if ( $row->rev_parent_id ) { $revIds[] = $row->rev_parent_id; } } @@ -255,7 +255,7 @@ class ApiQueryContributions extends ApiQueryBase { $this->addFieldsIf( 'rev_comment', $this->fld_comment || $this->fld_parsedcomment ); $this->addFieldsIf( 'rev_len', $this->fld_size || $this->fld_sizediff ); $this->addFieldsIf( 'rev_minor_edit', $this->fld_flags ); - $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff ); + $this->addFieldsIf( 'rev_parent_id', $this->fld_flags || $this->fld_sizediff || $this->fld_ids ); $this->addFieldsIf( 'rc_patrolled', $this->fld_patrolled ); if ( $this->fld_tags ) { @@ -268,8 +268,7 @@ class ApiQueryContributions extends ApiQueryBase { $this->addTables( 'change_tag' ); $this->addJoinConds( array( 'change_tag' => array( 'INNER JOIN', array( 'rev_id=ct_rev_id' ) ) ) ); $this->addWhereFld( 'ct_tag', $this->params['tag'] ); - global $wgOldChangeTagsIndex; - $index['change_tag'] = $wgOldChangeTagsIndex ? 'ct_tag' : 'change_tag_tag_id'; + $index['change_tag'] = 'change_tag_tag_id'; } if ( $this->params['toponly'] ) { @@ -297,6 +296,10 @@ class ApiQueryContributions extends ApiQueryBase { $vals['pageid'] = intval( $row->rev_page ); $vals['revid'] = intval( $row->rev_id ); // $vals['textid'] = intval( $row->rev_text_id ); // todo: Should this field be exposed? + + if ( !is_null( $row->rev_parent_id ) ) { + $vals['parentid'] = intval( $row->rev_parent_id ); + } } $title = Title::makeTitle( $row->page_namespace, $row->page_title ); @@ -474,7 +477,11 @@ class ApiQueryContributions extends ApiQueryBase { ), 'ids' => array( 'pageid' => 'integer', - 'revid' => 'integer' + 'revid' => 'integer', + 'parentid' => array( + ApiBase::PROP_TYPE => 'integer', + ApiBase::PROP_NULLABLE => true + ) ), 'title' => array( 'ns' => 'namespace', diff --git a/includes/api/ApiQueryUserInfo.php b/includes/api/ApiQueryUserInfo.php index 1a491eca..3c85ea69 100644 --- a/includes/api/ApiQueryUserInfo.php +++ b/includes/api/ApiQueryUserInfo.php @@ -104,12 +104,15 @@ class ApiQueryUserInfo extends ApiQueryBase { } if ( isset( $this->prop['preferencestoken'] ) && - is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) + is_null( $this->getMain()->getRequest()->getVal( 'callback' ) ) && + $user->isAllowed( 'editmyoptions' ) ) { $vals['preferencestoken'] = $user->getEditToken( '', $this->getMain()->getRequest() ); } if ( isset( $this->prop['editcount'] ) ) { + // use intval to prevent null if a non-logged-in user calls + // api.php?format=jsonfm&action=query&meta=userinfo&uiprop=editcount $vals['editcount'] = intval( $user->getEditCount() ); } @@ -121,11 +124,13 @@ class ApiQueryUserInfo extends ApiQueryBase { $vals['realname'] = $user->getRealName(); } - if ( isset( $this->prop['email'] ) ) { - $vals['email'] = $user->getEmail(); - $auth = $user->getEmailAuthenticationTimestamp(); - if ( !is_null( $auth ) ) { - $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth ); + if ( $user->isAllowed( 'viewmyprivateinfo' ) ) { + if ( isset( $this->prop['email'] ) ) { + $vals['email'] = $user->getEmail(); + $auth = $user->getEmailAuthenticationTimestamp(); + if ( !is_null( $auth ) ) { + $vals['emailauthenticated'] = wfTimestamp( TS_ISO_8601, $auth ); + } } } @@ -167,8 +172,9 @@ class ApiQueryUserInfo extends ApiQueryBase { if ( $user->isNewbie() ) { $categories[] = 'ip'; $categories[] = 'subnet'; - if ( !$user->isAnon() ) + if ( !$user->isAnon() ) { $categories[] = 'newbie'; + } } $categories = array_merge( $categories, $user->getGroups() ); diff --git a/includes/api/ApiQueryUsers.php b/includes/api/ApiQueryUsers.php index 72ab7866..dccfee67 100644 --- a/includes/api/ApiQueryUsers.php +++ b/includes/api/ApiQueryUsers.php @@ -127,7 +127,7 @@ class ApiQueryUsers extends ApiQueryBase { $this->addFields( array( 'user_name', 'ug_group' ) ); $userGroupsRes = $this->select( __METHOD__ ); - foreach( $userGroupsRes as $row ) { + foreach ( $userGroupsRes as $row ) { $userGroups[$row->user_name][] = $row->ug_group; } } @@ -149,7 +149,7 @@ class ApiQueryUsers extends ApiQueryBase { $data[$name]['name'] = $name; if ( isset( $this->prop['editcount'] ) ) { - $data[$name]['editcount'] = intval( $user->getEditCount() ); + $data[$name]['editcount'] = $user->getEditCount(); } if ( isset( $this->prop['registration'] ) ) { @@ -204,11 +204,13 @@ class ApiQueryUsers extends ApiQueryBase { } } + $context = $this->getContext(); // Second pass: add result data to $retval foreach ( $goodNames as $u ) { if ( !isset( $data[$u] ) ) { $data[$u] = array( 'name' => $u ); $urPage = new UserrightsPage; + $urPage->setContext( $context ); $iwUser = $urPage->fetchUser( $u ); if ( $iwUser instanceof UserRightsProxy ) { diff --git a/includes/api/ApiQueryWatchlist.php b/includes/api/ApiQueryWatchlist.php index 90b12c14..22843f50 100644 --- a/includes/api/ApiQueryWatchlist.php +++ b/includes/api/ApiQueryWatchlist.php @@ -68,7 +68,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->fld_user = isset( $prop['user'] ); $this->fld_userid = isset( $prop['userid'] ); $this->fld_comment = isset( $prop['comment'] ); - $this->fld_parsedcomment = isset ( $prop['parsedcomment'] ); + $this->fld_parsedcomment = isset( $prop['parsedcomment'] ); $this->fld_timestamp = isset( $prop['timestamp'] ); $this->fld_sizes = isset( $prop['sizes'] ); $this->fld_patrol = isset( $prop['patrol'] ); @@ -135,7 +135,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { if ( !$params['allrev'] ) { $this->addTables( 'page' ); - $this->addJoinConds( array( 'page' => array( 'LEFT JOIN','rc_cur_id=page_id' ) ) ); + $this->addJoinConds( array( 'page' => array( 'LEFT JOIN', 'rc_cur_id=page_id' ) ) ); $this->addWhere( 'rc_this_oldid=page_latest OR rc_type=' . RC_LOG ); } @@ -143,12 +143,11 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $show = array_flip( $params['show'] ); /* Check for conflicting parameters. */ - if ( ( isset ( $show['minor'] ) && isset ( $show['!minor'] ) ) - || ( isset ( $show['bot'] ) && isset ( $show['!bot'] ) ) - || ( isset ( $show['anon'] ) && isset ( $show['!anon'] ) ) - || ( isset ( $show['patrolled'] ) && isset ( $show['!patrolled'] ) ) - ) - { + if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) ) + || ( isset( $show['bot'] ) && isset( $show['!bot'] ) ) + || ( isset( $show['anon'] ) && isset( $show['!anon'] ) ) + || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) ) + ) { $this->dieUsageMsg( 'show' ); } @@ -171,6 +170,10 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) ); } + if ( !is_null( $params['type'] ) ) { + $this->addWhereFld( 'rc_type', $this->parseRCType( $params['type'] ) ); + } + if ( !is_null( $params['user'] ) && !is_null( $params['excludeuser'] ) ) { $this->dieUsage( 'user and excludeuser cannot be used together', 'user-excludeuser' ); } @@ -226,6 +229,32 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { private function extractRowInfo( $row ) { $vals = array(); + $type = intval( $row->rc_type ); + + /* Determine what kind of change this was. */ + switch ( $type ) { + case RC_EDIT: + $vals['type'] = 'edit'; + break; + case RC_NEW: + $vals['type'] = 'new'; + break; + case RC_MOVE: + $vals['type'] = 'move'; + break; + case RC_LOG: + $vals['type'] = 'log'; + break; + case RC_EXTERNAL: + $vals['type'] = 'external'; + break; + case RC_MOVE_OVER_REDIRECT: + $vals['type'] = 'move over redirect'; + break; + default: + $vals['type'] = $type; + } + if ( $this->fld_ids ) { $vals['pageid'] = intval( $row->rc_cur_id ); $vals['revid'] = intval( $row->rc_this_oldid ); @@ -312,6 +341,27 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { return $vals; } + /* Copied from ApiQueryRecentChanges. */ + private function parseRCType( $type ) { + if ( is_array( $type ) ) { + $retval = array(); + foreach ( $type as $t ) { + $retval[] = $this->parseRCType( $t ); + } + return $retval; + } + switch ( $type ) { + case 'edit': + return RC_EDIT; + case 'new': + return RC_NEW; + case 'log': + return RC_LOG; + case 'external': + return RC_EXTERNAL; + } + } + public function getAllowedParams() { return array( 'allrev' => false, @@ -321,7 +371,7 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'end' => array( ApiBase::PARAM_TYPE => 'timestamp' ), - 'namespace' => array ( + 'namespace' => array( ApiBase::PARAM_ISMULTI => true, ApiBase::PARAM_TYPE => 'namespace' ), @@ -376,6 +426,15 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { '!patrolled', ) ), + 'type' => array( + ApiBase::PARAM_ISMULTI => true, + ApiBase::PARAM_TYPE => array( + 'edit', + 'external', + 'new', + 'log', + ) + ), 'owner' => array( ApiBase::PARAM_TYPE => 'user' ), @@ -415,6 +474,13 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { 'Show only items that meet this criteria.', "For example, to see only minor edits done by logged-in users, set {$p}show=minor|!anon" ), + 'type' => array( + 'Which types of changes to show', + ' edit - Regular page edits', + ' external - External changes', + ' new - Page creations', + ' log - Log entries', + ), '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,6 +489,17 @@ class ApiQueryWatchlist extends ApiQueryGeneratorBase { public function getResultProperties() { global $wgLogTypes; return array( + '' => array( + 'type' => array( + ApiBase::PROP_TYPE => array( + 'edit', + 'new', + 'move', + 'log', + 'move over redirect' + ) + ) + ), 'ids' => array( 'pageid' => 'integer', 'revid' => 'integer', diff --git a/includes/api/ApiQueryWatchlistRaw.php b/includes/api/ApiQueryWatchlistRaw.php index 2cb4d9eb..ea4e724a 100644 --- a/includes/api/ApiQueryWatchlistRaw.php +++ b/includes/api/ApiQueryWatchlistRaw.php @@ -222,4 +222,8 @@ class ApiQueryWatchlistRaw extends ApiQueryGeneratorBase { 'api.php?action=query&generator=watchlistraw&gwrshow=changed&prop=revisions', ); } + + public function getHelpUrls() { + return 'https://www.mediawiki.org/wiki/API:Watchlistraw'; + } } diff --git a/includes/api/ApiRsd.php b/includes/api/ApiRsd.php index c4a1328c..d219c91c 100644 --- a/includes/api/ApiRsd.php +++ b/includes/api/ApiRsd.php @@ -40,7 +40,7 @@ class ApiRsd extends ApiBase { $service = array( 'apis' => $this->formatRsdApiList() ); ApiResult::setContent( $service, 'MediaWiki', 'engineName' ); ApiResult::setContent( $service, 'https://www.mediawiki.org/', 'engineLink' ); - ApiResult::setContent( $service, Title::newMainPage()->getCanonicalUrl(), 'homePageLink' ); + ApiResult::setContent( $service, Title::newMainPage()->getCanonicalURL(), 'homePageLink' ); $result->setIndexedTagName( $service['apis'], 'api' ); diff --git a/includes/api/ApiSetNotificationTimestamp.php b/includes/api/ApiSetNotificationTimestamp.php index 58d5d9ab..53a68fde 100644 --- a/includes/api/ApiSetNotificationTimestamp.php +++ b/includes/api/ApiSetNotificationTimestamp.php @@ -39,6 +39,9 @@ class ApiSetNotificationTimestamp extends ApiBase { if ( $user->isAnon() ) { $this->dieUsage( 'Anonymous users cannot use watchlist change notifications', 'notloggedin' ); } + if ( !$user->isAllowed( 'editmywatchlist' ) ) { + $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' ); + } $params = $this->extractRequestParams(); $this->requireMaxOneParameter( $params, 'timestamp', 'torevid', 'newerthanrevid' ); @@ -212,7 +215,7 @@ class ApiSetNotificationTimestamp extends ApiBase { } public function getParamDescription() { - return $this->getPageSet()->getParamDescription() + array( + return $this->getPageSet()->getFinalParamDescription() + array( 'entirewatchlist' => 'Work on all watched pages', 'timestamp' => 'Timestamp to which to set the notification timestamp', 'torevid' => 'Revision to set the notification timestamp to (one page only)', @@ -270,7 +273,7 @@ class ApiSetNotificationTimestamp extends ApiBase { $ps = $this->getPageSet(); return array_merge( parent::getPossibleErrors(), - $ps->getPossibleErrors(), + $ps->getFinalPossibleErrors(), $this->getRequireMaxOneParameterErrorMessages( array( 'timestamp', 'torevid', 'newerthanrevid' ) ), $this->getRequireOnlyOneParameterErrorMessages( diff --git a/includes/api/ApiUpload.php b/includes/api/ApiUpload.php index 7d67aa6e..467eccf8 100644 --- a/includes/api/ApiUpload.php +++ b/includes/api/ApiUpload.php @@ -56,15 +56,19 @@ class ApiUpload extends ApiBase { $this->mParams['chunk'] = $request->getFileName( 'chunk' ); // Copy the session key to the file key, for backward compatibility. - if( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) { + if ( !$this->mParams['filekey'] && $this->mParams['sessionkey'] ) { $this->mParams['filekey'] = $this->mParams['sessionkey']; } // Select an upload module - if ( !$this->selectUploadModule() ) { - return; // not a true upload, but a status request or similar - } elseif ( !isset( $this->mUpload ) ) { - $this->dieUsage( 'No upload module set', 'nomodule' ); + try { + if ( !$this->selectUploadModule() ) { + return; // not a true upload, but a status request or similar + } elseif ( !isset( $this->mUpload ) ) { + $this->dieUsage( 'No upload module set', 'nomodule' ); + } + } catch ( UploadStashException $e ) { // XXX: don't spam exception log + $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' ); } // First check permission to upload @@ -82,7 +86,7 @@ class ApiUpload extends ApiBase { // Check if the uploaded file is sane if ( $this->mParams['chunk'] ) { $maxSize = $this->mUpload->getMaxUploadSize(); - if( $this->mParams['filesize'] > $maxSize ) { + if ( $this->mParams['filesize'] > $maxSize ) { $this->dieUsage( 'The file you submitted was too large', 'file-too-large' ); } if ( !$this->mUpload->getTitle() ) { @@ -106,9 +110,13 @@ class ApiUpload extends ApiBase { } // Get the result based on the current upload context: - $result = $this->getContextResult(); - if ( $result['result'] === 'Success' ) { - $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() ); + try { + $result = $this->getContextResult(); + if ( $result['result'] === 'Success' ) { + $result['imageinfo'] = $this->mUpload->getImageInfo( $this->getResult() ); + } + } catch ( UploadStashException $e ) { // XXX: don't spam exception log + $this->dieUsage( get_class( $e ) . ": " . $e->getMessage(), 'stasherror' ); } $this->getResult()->addValue( null, $this->getModuleName(), $result ); @@ -144,7 +152,7 @@ class ApiUpload extends ApiBase { * @return array */ private function getStashResult( $warnings ) { - $result = array (); + $result = array(); // 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 { @@ -216,27 +224,27 @@ class ApiUpload extends ApiBase { // Check we added the last chunk: if ( $this->mParams['offset'] + $chunkSize == $this->mParams['filesize'] ) { if ( $this->mParams['async'] ) { - $progress = UploadBase::getSessionStatus( $this->mParams['filekey'] ); + $progress = UploadBase::getSessionStatus( $filekey ); if ( $progress && $progress['result'] === 'Poll' ) { $this->dieUsage( "Chunk assembly already in progress.", 'stashfailed' ); } UploadBase::setSessionStatus( - $this->mParams['filekey'], + $filekey, array( 'result' => 'Poll', 'stage' => 'queued', 'status' => Status::newGood() ) ); $ok = JobQueueGroup::singleton()->push( new AssembleUploadChunksJob( - Title::makeTitle( NS_FILE, $this->mParams['filekey'] ), + Title::makeTitle( NS_FILE, $filekey ), array( - 'filename' => $this->mParams['filename'], - 'filekey' => $this->mParams['filekey'], - 'session' => $this->getContext()->exportSession() + 'filename' => $this->mParams['filename'], + 'filekey' => $filekey, + 'session' => $this->getContext()->exportSession() ) ) ); if ( $ok ) { $result['result'] = 'Poll'; } else { - UploadBase::setSessionStatus( $this->mParams['filekey'], false ); + UploadBase::setSessionStatus( $filekey, false ); $this->dieUsage( "Failed to start AssembleUploadChunks.php", 'stashfailed' ); } @@ -358,11 +366,9 @@ class ApiUpload extends ApiBase { } if ( $this->mParams['chunk'] ) { - $this->checkChunkedEnabled(); - // Chunk upload $this->mUpload = new UploadFromChunks(); - if( isset( $this->mParams['filekey'] ) ) { + if ( isset( $this->mParams['filekey'] ) ) { // handle new chunk $this->mUpload->continueChunks( $this->mParams['filename'], @@ -404,6 +410,10 @@ class ApiUpload extends ApiBase { $this->dieUsageMsg( 'copyuploadbaddomain' ); } + if ( !UploadFromUrl::isAllowedUrl( $this->mParams['url'] ) ) { + $this->dieUsageMsg( 'copyuploadbadurl' ); + } + $async = false; if ( $this->mParams['asyncdownload'] ) { $this->checkAsyncDownloadEnabled(); @@ -452,9 +462,9 @@ class ApiUpload extends ApiBase { $verification = $this->mUpload->verifyUpload(); if ( $verification['status'] === UploadBase::OK ) { return; - } else { - return $this->checkVerification( $verification ); } + + $this->checkVerification( $verification ); } /** @@ -463,8 +473,8 @@ class ApiUpload extends ApiBase { protected function checkVerification( array $verification ) { global $wgFileExtensions; - // TODO: Move them to ApiBase's message map - switch( $verification['status'] ) { + // @todo Move them to ApiBase's message map + switch ( $verification['status'] ) { // Recoverable errors case UploadBase::MIN_LENGTH_PARTNAME: $this->dieRecoverableError( 'filename-tooshort', 'filename' ); @@ -494,7 +504,7 @@ class ApiUpload extends ApiBase { case UploadBase::FILETYPE_BADTYPE: $extradata = array( 'filetype' => $verification['finalExt'], - 'allowed' => $wgFileExtensions + 'allowed' => array_values( array_unique( $wgFileExtensions ) ) ); $this->getResult()->setIndexedTagName( $extradata['allowed'], 'ext' ); @@ -555,7 +565,8 @@ class ApiUpload extends ApiBase { if ( isset( $warnings['exists'] ) ) { $warning = $warnings['exists']; unset( $warnings['exists'] ); - $warnings[$warning['warning']] = $warning['file']->getName(); + $localFile = isset( $warning['normalizedFile'] ) ? $warning['normalizedFile'] : $warning['file']; + $warnings[$warning['warning']] = $localFile->getName(); } } return $warnings; @@ -596,12 +607,12 @@ class ApiUpload extends ApiBase { $ok = JobQueueGroup::singleton()->push( new PublishStashedFileJob( Title::makeTitle( NS_FILE, $this->mParams['filename'] ), array( - 'filename' => $this->mParams['filename'], - 'filekey' => $this->mParams['filekey'], - 'comment' => $this->mParams['comment'], - 'text' => $this->mParams['text'], - 'watch' => $watch, - 'session' => $this->getContext()->exportSession() + 'filename' => $this->mParams['filename'], + 'filekey' => $this->mParams['filekey'], + 'comment' => $this->mParams['comment'], + 'text' => $this->mParams['text'], + 'watch' => $watch, + 'session' => $this->getContext()->exportSession() ) ) ); if ( $ok ) { @@ -653,13 +664,6 @@ class ApiUpload extends ApiBase { } } - protected function checkChunkedEnabled() { - global $wgAllowChunkedUploads; - if ( !$wgAllowChunkedUploads ) { - $this->dieUsage( 'Chunked uploads disabled', 'chunkeduploaddisabled' ); - } - } - public function mustBePosted() { return true; } @@ -816,7 +820,7 @@ class ApiUpload extends ApiBase { array( 'code' => 'publishfailed', 'info' => 'Publishing of stashed file failed' ), array( 'code' => 'internal-error', 'info' => 'An internal error occurred' ), array( 'code' => 'asynccopyuploaddisabled', 'info' => 'Asynchronous copy uploads disabled' ), - array( 'code' => 'chunkeduploaddisabled', 'info' => 'Chunked uploads disabled' ), + array( 'code' => 'stasherror', 'info' => 'An upload stash error occurred' ), array( 'fileexists-forbidden' ), array( 'fileexists-shared-forbidden' ), ) diff --git a/includes/api/ApiUserrights.php b/includes/api/ApiUserrights.php index b9b1eeda..7d308285 100644 --- a/includes/api/ApiUserrights.php +++ b/includes/api/ApiUserrights.php @@ -38,6 +38,7 @@ class ApiUserrights extends ApiBase { $user = $this->getUrUser(); $form = new UserrightsPage; + $form->setContext( $this->getContext() ); $r['user'] = $user->getName(); $r['userid'] = $user->getId(); list( $r['added'], $r['removed'] ) = @@ -62,10 +63,10 @@ class ApiUserrights extends ApiBase { $params = $this->extractRequestParams(); $form = new UserrightsPage; + $form->setContext( $this->getContext() ); $status = $form->fetchUser( $params['user'] ); if ( !$status->isOK() ) { - $errors = $status->getErrorsArray(); - $this->dieUsageMsg( $errors[0] ); + $this->dieStatus( $status ); } else { $user = $status->value; } @@ -83,7 +84,7 @@ class ApiUserrights extends ApiBase { } public function getAllowedParams() { - return array ( + return array( 'user' => array( ApiBase::PARAM_TYPE => 'string', ApiBase::PARAM_REQUIRED => true diff --git a/includes/api/ApiWatch.php b/includes/api/ApiWatch.php index 3e51299f..c7d636a1 100644 --- a/includes/api/ApiWatch.php +++ b/includes/api/ApiWatch.php @@ -36,6 +36,9 @@ class ApiWatch extends ApiBase { if ( !$user->isLoggedIn() ) { $this->dieUsage( 'You must be logged-in to have a watchlist', 'notloggedin' ); } + if ( !$user->isAllowed( 'editmywatchlist' ) ) { + $this->dieUsage( 'You don\'t have permission to edit your watchlist', 'permissiondenied' ); + } $params = $this->extractRequestParams(); $title = Title::newFromText( $params['title'] ); @@ -57,19 +60,19 @@ class ApiWatch extends ApiBase { if ( $params['unwatch'] ) { $res['unwatched'] = ''; $res['message'] = $this->msg( 'removedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock(); - $success = UnwatchAction::doUnwatch( $title, $user ); + $status = UnwatchAction::doUnwatch( $title, $user ); } else { $res['watched'] = ''; $res['message'] = $this->msg( 'addedwatchtext', $title->getPrefixedText() )->title( $title )->parseAsBlock(); - $success = WatchAction::doWatch( $title, $user ); + $status = WatchAction::doWatch( $title, $user ); } if ( !is_null( $oldLang ) ) { $this->getContext()->setLanguage( $oldLang ); // Reset language to $oldLang } - if ( !$success ) { - $this->dieUsageMsg( 'hookaborted' ); + if ( !$status->isOK() ) { + $this->dieStatus( $status ); } $this->getResult()->addValue( null, $this->getModuleName(), $res ); } |