/**
* The MediaWiki class is the helper class for the index.php entry point.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class MediaWiki {
/**
$request = $this->context->getRequest();
$curid = $request->getInt( 'curid' );
$title = $request->getVal( 'title' );
- $action = $request->getVal( 'action', 'view' );
+ $action = $request->getVal( 'action' );
if ( $request->getCheck( 'search' ) ) {
// Compatibility with old search URLs which didn't use Special:Search
private function performRequest() {
global $wgTitle;
- wfProfileIn( __METHOD__ );
-
$request = $this->context->getRequest();
$requestTitle = $title = $this->context->getTitle();
$output = $this->context->getOutput();
|| $title->isSpecial( 'Badtitle' )
) {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
- wfProfileOut( __METHOD__ );
throw new BadTitleError();
}
$this->context->setTitle( $badTitle );
$wgTitle = $badTitle;
- wfProfileOut( __METHOD__ );
throw new PermissionsError( 'read', $permErrors );
}
$output->redirect( $url, 301 );
} else {
$this->context->setTitle( SpecialPage::getTitleFor( 'Badtitle' ) );
- wfProfileOut( __METHOD__ );
throw new BadTitleError();
}
// Redirect loops, no title in URL, $wgUsePathInfo URLs, and URLs with a variant
} elseif ( is_string( $article ) ) {
$output->redirect( $article );
} else {
- wfProfileOut( __METHOD__ );
throw new MWException( "Shouldn't happen: MediaWiki::initializeArticle()"
. " returned neither an object nor a URL" );
}
$user->addAutopromoteOnceGroups( 'onView' );
}
- wfProfileOut( __METHOD__ );
}
/**
* @return mixed An Article, or a string to redirect to another URL
*/
private function initializeArticle() {
- wfProfileIn( __METHOD__ );
$title = $this->context->getTitle();
if ( $this->context->canUseWikiPage() ) {
// NS_MEDIAWIKI has no redirects.
// It is also used for CSS/JS, so performance matters here...
if ( $title->getNamespace() == NS_MEDIAWIKI ) {
- wfProfileOut( __METHOD__ );
return $article;
}
if ( is_string( $target ) ) {
if ( !$this->config->get( 'DisableHardRedirects' ) ) {
// we'll need to redirect
- wfProfileOut( __METHOD__ );
return $target;
}
}
}
}
- wfProfileOut( __METHOD__ );
return $article;
}
* @param Title $requestTitle The original title, before any redirects were applied
*/
private function performAction( Page $page, Title $requestTitle ) {
- wfProfileIn( __METHOD__ );
$request = $this->context->getRequest();
$output = $this->context->getOutput();
if ( !Hooks::run( 'MediaWikiPerformAction',
array( $output, $page, $title, $user, $request, $this ) )
) {
- wfProfileOut( __METHOD__ );
return;
}
if ( $action instanceof Action ) {
# Let Squid cache things if we can purge them.
if ( $this->config->get( 'UseSquid' ) &&
- in_array( $request->getFullRequestURL(), $requestTitle->getSquidURLs() )
+ in_array(
+ // Use PROTO_INTERNAL because that's what getSquidURLs() uses
+ wfExpandUrl( $request->getRequestURL(), PROTO_INTERNAL ),
+ $requestTitle->getSquidURLs()
+ )
) {
$output->setSquidMaxage( $this->config->get( 'SquidMaxage' ) );
}
$action->show();
- wfProfileOut( __METHOD__ );
return;
}
$output->showErrorPage( 'nosuchaction', 'nosuchactiontext' );
}
- wfProfileOut( __METHOD__ );
}
/**
* @return bool
*/
private function checkMaxLag() {
- wfProfileIn( __METHOD__ );
$maxLag = $this->context->getRequest()->getVal( 'maxlag' );
if ( !is_null( $maxLag ) ) {
list( $host, $lag ) = wfGetLB()->getMaxLag();
echo "Waiting for a database server: $lag seconds lagged\n";
}
- wfProfileOut( __METHOD__ );
-
exit;
}
}
- wfProfileOut( __METHOD__ );
return true;
}
private function main() {
global $wgTitle;
- wfProfileIn( __METHOD__ );
-
$request = $this->context->getRequest();
// Send Ajax requests to the Ajax dispatcher.
- if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action', 'view' ) == 'ajax' ) {
-
+ if ( $this->config->get( 'UseAjax' ) && $request->getVal( 'action' ) === 'ajax' ) {
// Set a dummy title, because $wgTitle == null might break things
$title = Title::makeTitle( NS_MAIN, 'AJAX' );
$this->context->setTitle( $title );
$dispatcher = new AjaxDispatcher( $this->config );
$dispatcher->performAction( $this->context->getUser() );
- wfProfileOut( __METHOD__ );
return;
}
$action = $this->getAction();
$wgTitle = $title;
+ $trxProfiler = Profiler::instance()->getTransactionProfiler();
+
+ // Aside from rollback, master queries should not happen on GET requests.
+ // Periodic or "in passing" updates on GET should use the job queue.
+ if ( !$request->wasPosted()
+ && in_array( $action, array( 'view', 'edit', 'history' ) )
+ ) {
+ $trxProfiler->setExpectation( 'masterConns', 0, __METHOD__ );
+ $trxProfiler->setExpectation( 'writes', 0, __METHOD__ );
+ } else {
+ $trxProfiler->setExpectation( 'maxAffected', 500, __METHOD__ );
+ }
+
// If the user has forceHTTPS set to true, or if the user
// is in a group requiring HTTPS, or if they have the HTTPS
// preference set, redirect them to HTTPS.
$output->addVaryHeader( 'X-Forwarded-Proto' );
$output->redirect( $redirUrl );
$output->output();
- wfProfileOut( __METHOD__ );
return;
}
}
if ( $this->config->get( 'UseFileCache' ) && $title->getNamespace() >= 0 ) {
- wfProfileIn( 'main-try-filecache' );
if ( HTMLFileCache::useFileCache( $this->context ) ) {
// Try low-level file cache hit
$cache = new HTMLFileCache( $title, $action );
$this->context->getWikiPage()->doViewUpdates( $this->context->getUser() );
// Tell OutputPage that output is taken care of
$this->context->getOutput()->disable();
- wfProfileOut( 'main-try-filecache' );
- wfProfileOut( __METHOD__ );
return;
}
}
- wfProfileOut( 'main-try-filecache' );
}
// Actually do the work of the request and build up any output
// Output everything!
$this->context->getOutput()->output();
- wfProfileOut( __METHOD__ );
}
/**
* Ends this task peacefully
*/
public function restInPeace() {
+ // Ignore things like master queries/connections on GET requests
+ // as long as they are in deferred updates (which catch errors).
+ Profiler::instance()->getTransactionProfiler()->resetExpectations();
+
// Do any deferred jobs
DeferredUpdates::doUpdates( 'commit' );
return; // recursion guard
}
- $section = new ProfileSection( __METHOD__ );
-
if ( $jobRunRate < 1 ) {
$max = mt_getrandmax();
if ( mt_rand( 0, $max ) > $max * $jobRunRate ) {
$n = intval( $jobRunRate );
}
+ $runJobsLogger = MWLoggerFactory::getInstance( 'runJobs' );
+
if ( !$this->config->get( 'RunJobsAsync' ) ) {
// Fall back to running the job here while the user waits
- $runner = new JobRunner();
+ $runner = new JobRunner( $runJobsLogger );
$runner->run( array( 'maxJobs' => $n ) );
return;
}
);
wfRestoreWarnings();
if ( !$sock ) {
- wfDebugLog( 'runJobs', "Failed to start cron API (socket error $errno): $errstr\n" );
+ $runJobsLogger->error( "Failed to start cron API (socket error $errno): $errstr" );
// Fall back to running the job here while the user waits
- $runner = new JobRunner();
+ $runner = new JobRunner( $runJobsLogger );
$runner->run( array( 'maxJobs' => $n ) );
return;
}
$url = wfAppendQuery( wfScript( 'index' ), $query );
$req = "POST $url HTTP/1.1\r\nHost: {$info['host']}\r\nConnection: Close\r\nContent-Length: 0\r\n\r\n";
- wfDebugLog( 'runJobs', "Running $n job(s) via '$url'\n" );
+ $runJobsLogger->info( "Running $n job(s) via '$url'" );
// Send a cron API request to be performed in the background.
// Give up if this takes too long to send (which should be rare).
stream_set_timeout( $sock, 1 );
$bytes = fwrite( $sock, $req );
if ( $bytes !== strlen( $req ) ) {
- wfDebugLog( 'runJobs', "Failed to start cron API (socket write error)\n" );
+ $runJobsLogger->error( "Failed to start cron API (socket write error)" );
} else {
// Do not wait for the response (the script should handle client aborts).
// Make sure that we don't close before that script reaches ignore_user_abort().
$status = fgets( $sock );
if ( !preg_match( '#^HTTP/\d\.\d 202 #', $status ) ) {
- wfDebugLog( 'runJobs', "Failed to start cron API: received '$status'\n" );
+ $runJobsLogger->error( "Failed to start cron API: received '$status'" );
}
}
fclose( $sock );
* however, it does so inefficiently.
* @note Consider using a TitleValue object instead. TitleValue is more lightweight
* and does not rely on global state or the database.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class Title {
/** @var MapCacheLRU */
* Note that this doesn't pick up many things that could be wrong with titles, but that
* replacing this regex with something valid will make many titles valid.
*
- * @todo move this into MediaWikiTitleCodec
+ * @deprecated since 1.25, use MediaWikiTitleCodec::getTitleInvalidRegex() instead
*
* @return string Regex string
*/
static function getTitleInvalidRegex() {
- static $rxTc = false;
- if ( !$rxTc ) {
- # Matching titles will be held as illegal.
- $rxTc = '/' .
- # Any character not allowed is forbidden...
- '[^' . self::legalChars() . ']' .
- # URL percent encoding sequences interfere with the ability
- # to round-trip titles -- you can't link to them consistently.
- '|%[0-9A-Fa-f]{2}' .
- # XML/HTML character references produce similar issues.
- '|&[A-Za-z0-9\x80-\xff]+;' .
- '|&#[0-9]+;' .
- '|&#x[0-9A-Fa-f]+;' .
- '/S';
- }
-
- return $rxTc;
+ wfDeprecated( __METHOD__, '1.25' );
+ return MediaWikiTitleCodec::getTitleInvalidRegex();
}
/**
* @return string Content model id
*/
public function getContentModel( $flags = 0 ) {
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->mContentModel && $this->getArticleID( $flags ) ) {
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$this->mContentModel = $linkCache->getGoodLinkFieldObj( $this, 'model' );
}
return Title::makeTitle( $subjectNS, $this->getDBkey() );
}
+ /**
+ * Get the other title for this page, if this is a subject page
+ * get the talk page, if it is a subject page get the talk page
+ *
+ * @since 1.25
+ * @throws MWException
+ * @return Title
+ */
+ public function getOtherPage() {
+ if ( $this->isSpecialPage() ) {
+ throw new MWException( 'Special pages cannot have other pages' );
+ }
+ if ( $this->isTalkPage() ) {
+ return $this->getSubjectPage();
+ } else {
+ return $this->getTalkPage();
+ }
+ }
+
/**
* Get the default namespace index, for when there is no namespace
*
* @return string The URL
*/
public function getLinkURL( $query = '', $query2 = false, $proto = PROTO_RELATIVE ) {
- wfProfileIn( __METHOD__ );
if ( $this->isExternal() || $proto !== PROTO_RELATIVE ) {
$ret = $this->getFullURL( $query, $query2, $proto );
} elseif ( $this->getPrefixedText() === '' && $this->hasFragment() ) {
} else {
$ret = $this->getLocalURL( $query, $query2 ) . $this->getFragmentForURL();
}
- wfProfileOut( __METHOD__ );
return $ret;
}
* @param string $action Action that permission needs to be checked for
* @param User $user User to check (since 1.19); $wgUser will be used if not
* provided.
- * @param bool $doExpensiveQueries Set this to false to avoid doing
- * unnecessary queries.
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @return bool
*/
- public function userCan( $action, $user = null, $doExpensiveQueries = true ) {
+ public function userCan( $action, $user = null, $rigor = 'secure' ) {
if ( !$user instanceof User ) {
global $wgUser;
$user = $wgUser;
}
- return !count( $this->getUserPermissionsErrorsInternal(
- $action, $user, $doExpensiveQueries, true ) );
+ return !count( $this->getUserPermissionsErrorsInternal( $action, $user, $rigor, true ) );
}
/**
*
* @param string $action Action that permission needs to be checked for
* @param User $user User to check
- * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary
- * queries by skipping checks for cascading protections and user blocks.
+ * @param string $rigor One of (quick,full,secure)
+ * - quick : does cheap permission checks from slaves (usable for GUI creation)
+ * - full : does cheap and expensive checks possibly from a slave
+ * - secure : does cheap and expensive checks, using the master as needed
+ * @param bool $short Set this to true to stop after the first permission error.
* @param array $ignoreErrors Array of Strings Set this to a list of message keys
* whose corresponding errors may be ignored.
* @return array Array of arguments to wfMessage to explain permissions problems.
*/
- public function getUserPermissionsErrors( $action, $user, $doExpensiveQueries = true,
- $ignoreErrors = array()
+ public function getUserPermissionsErrors(
+ $action, $user, $rigor = 'secure', $ignoreErrors = array()
) {
- $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $doExpensiveQueries );
+ $errors = $this->getUserPermissionsErrorsInternal( $action, $user, $rigor );
// Remove the errors being ignored.
foreach ( $errors as $index => $error ) {
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkQuickPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkQuickPermissions( $action, $user, $errors, $rigor, $short ) {
if ( !Hooks::run( 'TitleQuickPermissions',
- array( $this, $user, $action, &$errors, $doExpensiveQueries, $short ) )
+ array( $this, $user, $action, &$errors, ( $rigor !== 'quick' ), $short ) )
) {
return $errors;
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPermissionHooks( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPermissionHooks( $action, $user, $errors, $rigor, $short ) {
// Use getUserPermissionsErrors instead
$result = '';
if ( !Hooks::run( 'userCan', array( &$this, &$user, $action, &$result ) ) ) {
}
// Check getUserPermissionsErrorsExpensive hook
if (
- $doExpensiveQueries
+ $rigor !== 'quick'
&& !( $short && count( $errors ) > 0 )
&& !Hooks::run( 'getUserPermissionsErrorsExpensive', array( &$this, &$user, $action, &$result ) )
) {
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkSpecialsAndNSPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkSpecialsAndNSPermissions( $action, $user, $errors, $rigor, $short ) {
# Only 'createaccount' can be performed on special pages,
# which don't actually exist in the DB.
if ( NS_SPECIAL == $this->mNamespace && $action !== 'createaccount' ) {
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCSSandJSPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkCSSandJSPermissions( $action, $user, $errors, $rigor, $short ) {
# Protect css/js subpages of user pages
# XXX: this might be better using restrictions
# XXX: right 'editusercssjs' is deprecated, for backward compatibility only
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkPageRestrictions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkPageRestrictions( $action, $user, $errors, $rigor, $short ) {
foreach ( $this->getRestrictions( $action ) as $right ) {
// Backwards compatibility, rewrite sysop -> editprotected
if ( $right == 'sysop' ) {
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkCascadingSourcesRestrictions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
- if ( $doExpensiveQueries && !$this->isCssJsSubpage() ) {
+ private function checkCascadingSourcesRestrictions( $action, $user, $errors, $rigor, $short ) {
+ if ( $rigor !== 'quick' && !$this->isCssJsSubpage() ) {
# We /could/ use the protection level on the source page, but it's
# fairly ugly as we have to establish a precedence hierarchy for pages
# included by multiple cascade-protected pages. So just restrict
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkActionPermissions( $action, $user, $errors,
- $doExpensiveQueries, $short
- ) {
+ private function checkActionPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgDeleteRevisionsLimit, $wgLang;
if ( $action == 'protect' ) {
- if ( count( $this->getUserPermissionsErrorsInternal( 'edit',
- $user, $doExpensiveQueries, true ) )
- ) {
+ if ( count( $this->getUserPermissionsErrorsInternal( 'edit', $user, $rigor, true ) ) ) {
// If they can't edit, they shouldn't protect.
$errors[] = array( 'protect-cantedit' );
}
$errors[] = array( 'immobile-target-page' );
}
} elseif ( $action == 'delete' ) {
- $tempErrors = $this->checkPageRestrictions( 'edit',
- $user, array(), $doExpensiveQueries, true );
+ $tempErrors = $this->checkPageRestrictions( 'edit', $user, array(), $rigor, true );
if ( !$tempErrors ) {
$tempErrors = $this->checkCascadingSourcesRestrictions( 'edit',
- $user, $tempErrors, $doExpensiveQueries, true );
+ $user, $tempErrors, $rigor, true );
}
if ( $tempErrors ) {
// If protection keeps them from editing, they shouldn't be able to delete.
$errors[] = array( 'deleteprotected' );
}
- if ( $doExpensiveQueries && $wgDeleteRevisionsLimit
+ if ( $rigor !== 'quick' && $wgDeleteRevisionsLimit
&& !$this->userCan( 'bigdelete', $user ) && $this->isBigDeletion()
) {
$errors[] = array( 'delete-toobig', $wgLang->formatNum( $wgDeleteRevisionsLimit ) );
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkUserBlock( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkUserBlock( $action, $user, $errors, $rigor, $short ) {
// Account creation blocks handled at userlogin.
// Unblocking handled in SpecialUnblock
- if ( !$doExpensiveQueries || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
+ if ( $rigor === 'quick' || in_array( $action, array( 'createaccount', 'unblock' ) ) ) {
return $errors;
}
$errors[] = array( 'confirmedittext' );
}
- if ( ( $action == 'edit' || $action == 'create' ) && !$user->isBlockedFrom( $this ) ) {
+ $useSlave = ( $rigor !== 'secure' );
+ if ( ( $action == 'edit' || $action == 'create' )
+ && !$user->isBlockedFrom( $this, $useSlave )
+ ) {
// Don't block the user from editing their own talk page unless they've been
// explicitly blocked from that too.
- } elseif ( $user->isBlocked() && $user->mBlock->prevents( $action ) !== false ) {
+ } elseif ( $user->isBlocked() && $user->getBlock()->prevents( $action ) !== false ) {
// @todo FIXME: Pass the relevant context into this function.
$errors[] = $user->getBlock()->getPermissionsError( RequestContext::getMain() );
}
* @param string $action The action to check
* @param User $user User to check
* @param array $errors List of current errors
- * @param bool $doExpensiveQueries Whether or not to perform expensive queries
+ * @param string $rigor Same format as Title::getUserPermissionsErrors()
* @param bool $short Short circuit on first error
*
* @return array List of errors
*/
- private function checkReadPermissions( $action, $user, $errors, $doExpensiveQueries, $short ) {
+ private function checkReadPermissions( $action, $user, $errors, $rigor, $short ) {
global $wgWhitelistRead, $wgWhitelistReadRegexp;
$whitelisted = false;
*
* @param string $action Action that permission needs to be checked for
* @param User $user User to check
- * @param bool $doExpensiveQueries Set this to false to avoid doing unnecessary queries.
+ * @param string $rigor One of (quick,full,secure)
+ * - quick : does cheap permission checks from slaves (usable for GUI creation)
+ * - full : does cheap and expensive checks possibly from a slave
+ * - secure : does cheap and expensive checks, using the master as needed
* @param bool $short Set this to true to stop after the first permission error.
* @return array Array of arrays of the arguments to wfMessage to explain permissions problems.
*/
- protected function getUserPermissionsErrorsInternal( $action, $user,
- $doExpensiveQueries = true, $short = false
+ protected function getUserPermissionsErrorsInternal(
+ $action, $user, $rigor = 'secure', $short = false
) {
- wfProfileIn( __METHOD__ );
+ if ( $rigor === true ) {
+ $rigor = 'secure'; // b/c
+ } elseif ( $rigor === false ) {
+ $rigor = 'quick'; // b/c
+ } elseif ( !in_array( $rigor, array( 'quick', 'full', 'secure' ) ) ) {
+ throw new Exception( "Invalid rigor parameter '$rigor'." );
+ }
# Read has special handling
if ( $action == 'read' ) {
while ( count( $checks ) > 0 &&
!( $short && count( $errors ) > 0 ) ) {
$method = array_shift( $checks );
- $errors = $this->$method( $action, $user, $errors, $doExpensiveQueries, $short );
+ $errors = $this->$method( $action, $user, $errors, $rigor, $short );
}
- wfProfileOut( __METHOD__ );
return $errors;
}
return array( $this->mHasCascadingRestrictions, $pagerestrictions );
}
- wfProfileIn( __METHOD__ );
-
$dbr = wfGetDB( DB_SLAVE );
if ( $this->getNamespace() == NS_FILE ) {
$sources = $getPages ? array() : false;
$now = wfTimestampNow();
- $purgeExpired = false;
foreach ( $res as $row ) {
$expiry = $wgContLang->formatExpiry( $row->pr_expiry, TS_MW );
} else {
$sources = true;
}
- } else {
- // Trigger lazy purge of expired restrictions from the db
- $purgeExpired = true;
}
}
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
if ( $getPages ) {
$this->mCascadeSources = $sources;
$this->mHasCascadingRestrictions = $sources;
}
- wfProfileOut( __METHOD__ );
return array( $sources, $pagerestrictions );
}
if ( count( $rows ) ) {
# Current system - load second to make them override.
$now = wfTimestampNow();
- $purgeExpired = false;
# Cycle through all the restrictions.
foreach ( $rows as $row ) {
$this->mRestrictions[$row->pr_type] = explode( ',', trim( $row->pr_level ) );
$this->mCascadeRestriction |= $row->pr_cascade;
- } else {
- // Trigger a lazy purge of expired restrictions
- $purgeExpired = true;
}
}
-
- if ( $purgeExpired ) {
- Title::purgeExpiredRestrictions();
- }
}
$this->mRestrictionsLoaded = true;
$this->mRestrictionsExpiry['create'] = $expiry;
$this->mRestrictions['create'] = explode( ',', trim( $title_protection['permission'] ) );
} else { // Get rid of the old restrictions
- Title::purgeExpiredRestrictions();
$this->mTitleProtection = false;
}
} else {
if ( !is_null( $this->mRedirect ) ) {
return $this->mRedirect;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mRedirect = false;
return $this->mRedirect;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'redirect' );
if ( $cached === null ) {
# Trust LinkCache's state over our own
if ( $this->mLength != -1 ) {
return $this->mLength;
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLength = 0;
return $this->mLength;
}
$linkCache = LinkCache::singleton();
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'length' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
if ( !( $flags & Title::GAID_FOR_UPDATE ) && $this->mLatestID !== false ) {
return intval( $this->mLatestID );
}
- # Calling getArticleID() loads the field from cache as needed
if ( !$this->getArticleID( $flags ) ) {
$this->mLatestID = 0;
return $this->mLatestID;
}
$linkCache = LinkCache::singleton();
- $linkCache->addLinkObj( $this );
+ $linkCache->addLinkObj( $this ); # in case we already had an article ID
$cached = $linkCache->getGoodLinkFieldObj( $this, 'revision' );
if ( $cached === null ) {
# Trust LinkCache's state over our own, as for isRedirect()
*
* @deprecated since 1.25, use MovePage's methods instead
* @param Title $nt The new title
- * @param bool $auth Ignored
+ * @param bool $auth Whether to check user permissions (uses $wgUser)
* @param string $reason Is the log summary of the move, used for spam checking
* @return array|bool True on success, getUserPermissionsErrors()-like array on failure
*/
}
$mp = new MovePage( $this, $nt );
- $errors = wfMergeErrorArrays(
- $mp->isValidMove()->getErrorsArray(),
- $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
- );
+ $errors = $mp->isValidMove()->getErrorsArray();
+ if ( $auth ) {
+ $errors = wfMergeErrorArrays(
+ $errors,
+ $mp->checkPermissions( $wgUser, $reason )->getErrorsArray()
+ );
+ }
return $errors ? : true;
}
$errors = array();
$destFile = wfLocalFile( $nt );
- if ( !$wgUser->isAllowed( 'reupload-shared' ) && !$destFile->exists() && wfFindFile( $nt ) ) {
+ $destFile->load( File::READ_LATEST );
+ if ( !$wgUser->isAllowed( 'reupload-shared' )
+ && !$destFile->exists() && wfFindFile( $nt )
+ ) {
$errors[] = array( 'file-exists-sharedrepo' );
}
# Is it an existing file?
if ( $nt->getNamespace() == NS_FILE ) {
$file = wfLocalFile( $nt );
+ $file->load( File::READ_LATEST );
if ( $file->exists() ) {
wfDebug( __METHOD__ . ": file exists\n" );
return false;
if ( $this->mIsBigDeletion === null ) {
$dbr = wfGetDB( DB_SLAVE );
- $innerQuery = $dbr->selectSQLText(
+ $revCount = $dbr->selectRowCount(
'revision',
'1',
array( 'rev_page' => $this->getArticleID() ),
array( 'LIMIT' => $wgDeleteRevisionsLimit + 1 )
);
- $revCount = $dbr->query(
- 'SELECT COUNT(*) FROM (' . $innerQuery . ') AS innerQuery',
- __METHOD__
- );
- $revCount = $revCount->fetchRow();
- $revCount = $revCount['COUNT(*)'];
-
$this->mIsBigDeletion = $revCount > $wgDeleteRevisionsLimit;
}
'rev_timestamp < ' . $dbr->addQuotes( $dbr->timestamp( $new->getTimestamp() ) )
);
if ( $max !== null ) {
- $res = $dbr->select( 'revision', '1',
+ return $dbr->selectRowCount( 'revision', '1',
$conds,
__METHOD__,
array( 'LIMIT' => $max + 1 ) // extra to detect truncation
);
- return $res->numRows();
} else {
return (int)$dbr->selectField( 'revision', 'count(*)', $conds, __METHOD__ );
}
}
// No DB query needed if $old and $new are the same or successive revisions:
if ( $old->getId() === $new->getId() ) {
- return ( $old_cmp === '>' && $new_cmp === '<' ) ? array() : array( $old->getRawUserText() );
+ return ( $old_cmp === '>' && $new_cmp === '<' ) ?
+ array() :
+ array( $old->getUserText( Revision::RAW ) );
} elseif ( $old->getId() === $new->getParentId() ) {
if ( $old_cmp === '>=' && $new_cmp === '<=' ) {
- $authors[] = $old->getRawUserText();
- if ( $old->getRawUserText() != $new->getRawUserText() ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
+ if ( $old->getUserText( Revision::RAW ) != $new->getUserText( Revision::RAW ) ) {
+ $authors[] = $new->getUserText( Revision::RAW );
}
} elseif ( $old_cmp === '>=' ) {
- $authors[] = $old->getRawUserText();
+ $authors[] = $old->getUserText( Revision::RAW );
} elseif ( $new_cmp === '<=' ) {
- $authors[] = $new->getRawUserText();
+ $authors[] = $new->getUserText( Revision::RAW );
}
return $authors;
}
*/
public function getPageLanguage() {
global $wgLang, $wgLanguageCode;
- wfProfileIn( __METHOD__ );
if ( $this->isSpecialPage() ) {
// special pages are in the user language
- wfProfileOut( __METHOD__ );
return $wgLang;
}
// Checking if DB language is set
if ( $this->mDbPageLanguage ) {
- wfProfileOut( __METHOD__ );
return wfGetLangObj( $this->mDbPageLanguage );
}
$langObj = wfGetLangObj( $this->mPageLanguage[0] );
}
- wfProfileOut( __METHOD__ );
return $langObj;
}
public function getEditNotices( $oldid = 0 ) {
$notices = array();
- # Optional notices on a per-namespace and per-page basis
+ // Optional notice for the entire namespace
$editnotice_ns = 'editnotice-' . $this->getNamespace();
- $editnotice_ns_message = wfMessage( $editnotice_ns );
- if ( $editnotice_ns_message->exists() ) {
- $notices[$editnotice_ns] = $editnotice_ns_message->parseAsBlock();
+ $msg = wfMessage( $editnotice_ns );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ // Edit notices may have complex logic, but output nothing (T91715)
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_ns] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-namespace',
+ Sanitizer::escapeClass( "mw-$editnotice_ns" )
+ ) ),
+ $html
+ );
+ }
}
+
if ( MWNamespace::hasSubpages( $this->getNamespace() ) ) {
+ // Optional notice for page itself and any parent page
$parts = explode( '/', $this->getDBkey() );
$editnotice_base = $editnotice_ns;
while ( count( $parts ) > 0 ) {
$editnotice_base .= '-' . array_shift( $parts );
- $editnotice_base_msg = wfMessage( $editnotice_base );
- if ( $editnotice_base_msg->exists() ) {
- $notices[$editnotice_base] = $editnotice_base_msg->parseAsBlock();
+ $msg = wfMessage( $editnotice_base );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnotice_base] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-base',
+ Sanitizer::escapeClass( "mw-$editnotice_base" )
+ ) ),
+ $html
+ );
+ }
}
}
} else {
- # Even if there are no subpages in namespace, we still don't want / in MW ns.
+ // Even if there are no subpages in namespace, we still don't want "/" in MediaWiki message keys
$editnoticeText = $editnotice_ns . '-' . str_replace( '/', '-', $this->getDBkey() );
- $editnoticeMsg = wfMessage( $editnoticeText );
- if ( $editnoticeMsg->exists() ) {
- $notices[$editnoticeText] = $editnoticeMsg->parseAsBlock();
+ $msg = wfMessage( $editnoticeText );
+ if ( $msg->exists() ) {
+ $html = $msg->parseAsBlock();
+ if ( trim( $html ) !== '' ) {
+ $notices[$editnoticeText] = Html::rawElement(
+ 'div',
+ array( 'class' => array(
+ 'mw-editnotice',
+ 'mw-editnotice-page',
+ Sanitizer::escapeClass( "mw-$editnoticeText" )
+ ) ),
+ $html
+ );
+ }
}
}
* of memory.
*
* Introduced by r47317
- *
- * @internal documentation reviewed on 18 Mar 2011 by hashar
*/
class BacklinkCache {
/** @var ProcessCacheLRU */
* @return ResultWrapper
*/
protected function queryLinks( $table, $startId, $endId, $max, $select = 'all' ) {
- wfProfileIn( __METHOD__ );
$fromField = $this->getPrefix( $table ) . '_from';
}
}
- wfProfileOut( __METHOD__ );
-
return $res;
}
return array( 'numRows' => $numRows, 'batches' => $batches );
}
+
+ /**
+ * Get a Title iterator for cascade-protected template/file use backlinks
+ *
+ * @return TitleArray
+ * @since 1.25
+ */
+ public function getCascadeProtectedLinks() {
+ $dbr = $this->getDB();
+
+ // @todo: use UNION without breaking tests that use temp tables
+ $resSets = array();
+ $resSets[] = $dbr->select(
+ array( 'templatelinks', 'page_restrictions', 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ array(
+ 'tl_namespace' => $this->title->getNamespace(),
+ 'tl_title' => $this->title->getDBkey(),
+ 'tl_from = pr_page',
+ 'pr_cascade' => 1,
+ 'page_id = tl_from'
+ ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ if ( $this->title->getNamespace() == NS_FILE ) {
+ $resSets[] = $dbr->select(
+ array( 'imagelinks', 'page_restrictions', 'page' ),
+ array( 'page_namespace', 'page_title', 'page_id' ),
+ array(
+ 'il_to' => $this->title->getDBkey(),
+ 'il_from = pr_page',
+ 'pr_cascade' => 1,
+ 'page_id = il_from'
+ ),
+ __METHOD__,
+ array( 'DISTINCT' )
+ );
+ }
+
+ // Combine and de-duplicate the results
+ $mergedRes = array();
+ foreach ( $resSets as $res ) {
+ foreach ( $res as $row ) {
+ $mergedRes[$row->page_id] = $row;
+ }
+ }
+
+ return TitleArray::newFromResult(
+ new FakeResultWrapper( array_values( $mergedRes ) ) );
+ }
}
* See design.txt for an overview.
* Note: edit user interface and cache support functions have been
* moved to separate EditPage and HTMLFileCache classes.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class Article implements Page {
/** @var IContextSource The context this Article is executed in */
* @since 1.21
*/
protected function getContentObject() {
- wfProfileIn( __METHOD__ );
if ( $this->mPage->getID() === 0 ) {
# If this is a MediaWiki:x message, then load the messages
$content = $this->mContentObject;
}
- wfProfileOut( __METHOD__ );
return $content;
}
return $this->mContent;
}
- wfProfileIn( __METHOD__ );
-
$content = $this->fetchContentObject();
if ( !$content ) {
- wfProfileOut( __METHOD__ );
return false;
}
$this->mContent = ContentHandler::getContentText( $content );
ContentHandler::runLegacyHooks( 'ArticleAfterFetchContent', array( &$this, &$this->mContent ) );
- wfProfileOut( __METHOD__ );
-
return $this->mContent;
}
return $this->mContentObject;
}
- wfProfileIn( __METHOD__ );
-
$this->mContentLoaded = true;
$this->mContent = null;
$this->mRevision = Revision::newFromId( $oldid );
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
- wfProfileOut( __METHOD__ );
return false;
}
}
if ( !$this->mPage->getLatest() ) {
wfDebug( __METHOD__ . " failed to find page data for title " .
$this->getTitle()->getPrefixedText() . "\n" );
- wfProfileOut( __METHOD__ );
return false;
}
if ( !$this->mRevision ) {
wfDebug( __METHOD__ . " failed to retrieve current page, rev_id " .
$this->mPage->getLatest() . "\n" );
- wfProfileOut( __METHOD__ );
return false;
}
}
// @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
// We should instead work with the Revision object when we need it...
// Loads if user is allowed
- $this->mContentObject = $this->mRevision->getContent(
+ $content = $this->mRevision->getContent(
Revision::FOR_THIS_USER,
$this->getContext()->getUser()
);
+
+ if ( !$content ) {
+ wfDebug( __METHOD__ . " failed to retrieve content of revision " .
+ $this->mRevision->getId() . "\n" );
+ return false;
+ }
+
+ $this->mContentObject = $content;
$this->mRevIdFetched = $this->mRevision->getId();
Hooks::run( 'ArticleAfterFetchContentObject', array( &$this, &$this->mContentObject ) );
- wfProfileOut( __METHOD__ );
-
return $this->mContentObject;
}
public function view() {
global $wgUseFileCache, $wgUseETag, $wgDebugToolbar, $wgMaxRedirects;
- wfProfileIn( __METHOD__ );
-
# Get variables from query string
# As side effect this will load the revision and update the title
# in a revision ID is passed in the request, so this should remain
$permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
if ( count( $permErrors ) ) {
wfDebug( __METHOD__ . ": denied on secondary read check\n" );
- wfProfileOut( __METHOD__ );
throw new PermissionsError( 'read', $permErrors );
}
if ( $this->mRedirectUrl ) {
$outputPage->redirect( $this->mRedirectUrl );
wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
- wfProfileOut( __METHOD__ );
return;
}
if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
wfDebug( __METHOD__ . ": showing diff page\n" );
$this->showDiffPage();
- wfProfileOut( __METHOD__ );
return;
}
# Is it client cached?
if ( $outputPage->checkLastModified( $timestamp ) ) {
wfDebug( __METHOD__ . ": done 304\n" );
- wfProfileOut( __METHOD__ );
return;
# Try file cache
# tell wgOut that output is taken care of
$outputPage->disable();
$this->mPage->doViewUpdates( $user, $oldid );
- wfProfileOut( __METHOD__ );
return;
}
$useParserCache = $this->mPage->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $user->getStubThreshold() ) {
- wfIncrStats( 'pcache_miss_stub' );
+ $this->getContext()->getStats()->increment( 'pcache_miss_stub' );
}
$this->showRedirectedFromHeader();
wfDebug( __METHOD__ . ": showing missing article\n" );
$this->showMissingArticle();
$this->mPage->doViewUpdates( $user );
- wfProfileOut( __METHOD__ );
return;
}
if ( !$this->showDeletedRevisionHeader() ) {
wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
- wfProfileOut( __METHOD__ );
return;
}
}
$outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
}
# Connection or timeout error
- wfProfileOut( __METHOD__ );
return;
}
$this->mParserOutput = $poolArticleView->getParserOutput();
$outputPage->addParserOutput( $this->mParserOutput );
if ( $content->getRedirectTarget() ) {
- $outputPage->addSubtitle(
- "<span id=\"redirectsub\">" . wfMessage( 'redirectpagesub' )->parse() . "</span>"
- );
+ $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
+ $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
}
# Don't cache a dirty ParserOutput object
}
# Get the ParserOutput actually *displayed* here.
- # Note that $this->mParserOutput is the *current* version output.
+ # Note that $this->mParserOutput is the *current*/oldid version output.
$pOutput = ( $outputDone instanceof ParserOutput )
? $outputDone // object fetched by hook
: $this->mParserOutput;
$outputPage->addModules( 'mediawiki.action.view.postEdit' );
- wfProfileOut( __METHOD__ );
}
/**
* Show a diff page according to current request variables. For use within
* Article::view() only, other callers should use the DifferenceEngine class.
*
- * @todo Make protected
*/
- public function showDiffPage() {
+ protected function showDiffPage() {
$request = $this->getContext()->getRequest();
$user = $this->getContext()->getUser();
$diff = $request->getVal( 'diff' );
if ( $showCacheHint ) {
$dir = $this->getContext()->getLanguage()->getDir();
- $lang = $this->getContext()->getLanguage()->getCode();
+ $lang = $this->getContext()->getLanguage()->getHtmlCode();
$outputPage->wrapWikiMsg(
"<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
*/
public function showRedirectedFromHeader() {
global $wgRedirectSources;
- $outputPage = $this->getContext()->getOutput();
- $request = $this->getContext()->getRequest();
+ $context = $this->getContext();
+ $outputPage = $context->getOutput();
+ $request = $context->getRequest();
$rdfrom = $request->getVal( 'rdfrom' );
// Construct a URL for the current page view, but with the target title
);
$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
- wfMessage( 'redirectedfrom' )->rawParams( $redir )->parse()
+ $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
. "</span>" );
// Add the script to update the displayed URL and
if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
$redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
$outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
- wfMessage( 'redirectedfrom' )->rawParams( $redir )->parse()
+ $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
. "</span>" );
// Add the script to update the displayed URL
return false;
}
- wfProfileIn( __METHOD__ );
-
// New page patrol: Get the timestamp of the oldest revison which
// the revision table holds for the given page. Then we look
// whether it's within the RC lifespan and if it is, we try
// Check for cached results
if ( $cache->get( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ) ) ) {
- wfProfileOut( __METHOD__ );
return false;
}
) {
// The current revision is already older than what could be in the RC table
// 6h tolerance because the RC might not be cleaned out regularly
- wfProfileOut( __METHOD__ );
return false;
}
// Don't cache in case we can patrol as this could change
$cache->set( wfMemcKey( 'NotPatrollablePage', $this->getTitle()->getArticleID() ), '1' );
- wfProfileOut( __METHOD__ );
return false;
}
- if ( $rc->getPerformer()->getName() == $user->getName() ) {
+ if ( $rc->getPerformer()->equals( $user ) ) {
// Don't show a patrol link for own creations. If the user could
// patrol them, they already would be patrolled
- wfProfileOut( __METHOD__ );
return false;
}
'</div>'
);
- wfProfileOut( __METHOD__ );
return true;
}
return;
}
- $unhide = $this->getContext()->getRequest()->getInt( 'unhide' ) == 1;
+ $context = $this->getContext();
+ $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
# Cascade unhide param in links for easy deletion browsing
$extraParams = array();
$timestamp = $revision->getTimestamp();
$current = ( $oldid == $this->mPage->getLatest() );
- $language = $this->getContext()->getLanguage();
- $user = $this->getContext()->getUser();
+ $language = $context->getLanguage();
+ $user = $context->getUser();
$td = $language->userTimeAndDate( $timestamp, $user );
$tddate = $language->userDate( $timestamp, $user );
# Show user links if allowed to see them. If hidden, then show them only if requested...
$userlinks = Linker::revUserTools( $revision, !$unhide );
- $infomsg = $current && !wfMessage( 'revision-info-current' )->isDisabled()
+ $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
? 'revision-info-current'
: 'revision-info';
- $outputPage = $this->getContext()->getOutput();
+ $outputPage = $context->getOutput();
$outputPage->addSubtitle( "<div id=\"mw-{$infomsg}\">" .
- wfMessage( $infomsg, $td )
+ $context->msg( $infomsg, $td )
->rawParams( $userlinks )
->params( $revision->getID(), $tddate, $tdtime, $revision->getUserText() )
->rawParams( Linker::revComment( $revision, true, true ) )
);
$lnk = $current
- ? wfMessage( 'currentrevisionlink' )->escaped()
+ ? $context->msg( 'currentrevisionlink' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'currentrevisionlink' )->escaped(),
+ $context->msg( 'currentrevisionlink' )->escaped(),
array(),
$extraParams
);
$curdiff = $current
- ? wfMessage( 'diff' )->escaped()
+ ? $context->msg( 'diff' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'cur',
$prevlink = $prev
? Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'previousrevision' )->escaped(),
+ $context->msg( 'previousrevision' )->escaped(),
array(),
array(
'direction' => 'prev',
'oldid' => $oldid
) + $extraParams
)
- : wfMessage( 'previousrevision' )->escaped();
+ : $context->msg( 'previousrevision' )->escaped();
$prevdiff = $prev
? Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'prev',
'oldid' => $oldid
) + $extraParams
)
- : wfMessage( 'diff' )->escaped();
+ : $context->msg( 'diff' )->escaped();
$nextlink = $current
- ? wfMessage( 'nextrevision' )->escaped()
+ ? $context->msg( 'nextrevision' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'nextrevision' )->escaped(),
+ $context->msg( 'nextrevision' )->escaped(),
array(),
array(
'direction' => 'next',
) + $extraParams
);
$nextdiff = $current
- ? wfMessage( 'diff' )->escaped()
+ ? $context->msg( 'diff' )->escaped()
: Linker::linkKnown(
$this->getTitle(),
- wfMessage( 'diff' )->escaped(),
+ $context->msg( 'diff' )->escaped(),
array(),
array(
'diff' => 'next',
}
$outputPage->addSubtitle( "<div id=\"mw-revision-nav\">" . $cdel .
- wfMessage( 'revision-nav' )->rawParams(
+ $context->msg( 'revision-nav' )->rawParams(
$prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
)->escaped() . "</div>" );
}
$lang = $this->getTitle()->getPageLanguage();
$out = $this->getContext()->getOutput();
if ( $appendSubtitle ) {
- $out->addSubtitle( wfMessage( 'redirectpagesub' )->parse() );
+ $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
}
$out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
( $forceKnown ? array( 'known', 'noclasses' ) : array() )
) . '</li>';
}
+ $html .= '</ul>';
- $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->text();
+ $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
return '<div class="redirectMsg">' .
'<p>' . $redirectToText . '</p>' .
if ( !$reason ) {
try {
$reason = $this->generateReason( $hasHistory );
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
# if a page is horribly broken, we still want to be able to
# delete it. So be lenient about errors here.
wfDebug( "Error while building auto delete summary: $e" );
$context->msg( 'historywarning' )->numParams( $revisions )->parse() .
$context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
$context->msg( 'history' )->escaped(),
- array( 'rel' => 'archives' ),
+ array(),
array( 'action' => 'history' ) ) .
'</strong>'
);
*
* Some fields are public only for backwards-compatibility. Use accessors.
* In the past, this class was part of Article.php and everything was public.
- *
- * @internal documentation reviewed 15 Mar 2010
*/
class WikiPage implements Page, IDBAccessObject {
// Constants for $mDataLoadedFrom and related
* @return Revision|null
*/
public function getOldestRevision() {
- wfProfileIn( __METHOD__ );
// Try using the slave database first, then try the master
$continue = 2;
}
}
- wfProfileOut( __METHOD__ );
return $row ? Revision::newFromRow( $row ) : null;
}
* @return array Array of authors, duplicates not removed
*/
public function getLastNAuthors( $num, $revLatest = 0 ) {
- wfProfileIn( __METHOD__ );
// First try the slave
// If that doesn't have the latest revision, try the master
$continue = 2;
);
if ( !$res ) {
- wfProfileOut( __METHOD__ );
return array();
}
$authors[] = $row->rev_user_text;
}
- wfProfileOut( __METHOD__ );
return $authors;
}
* @return ParserOutput|bool ParserOutput or false if the revision was not found
*/
public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
- wfProfileIn( __METHOD__ );
$useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
if ( $useParserCache ) {
$parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
if ( $parserOutput !== false ) {
- wfProfileOut( __METHOD__ );
return $parserOutput;
}
}
$pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
$pool->execute();
- wfProfileOut( __METHOD__ );
-
return $pool->getParserOutput();
}
* @return int The newly created page_id key, or false if the title already existed
*/
public function insertOn( $dbw ) {
- wfProfileIn( __METHOD__ );
$page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
$dbw->insert( 'page', array(
$this->mId = $newid;
$this->mTitle->resetArticleID( $newid );
}
- wfProfileOut( __METHOD__ );
return $affected ? $newid : false;
}
) {
global $wgContentHandlerUseDB;
- wfProfileIn( __METHOD__ );
-
$content = $revision->getContent();
$len = $content ? $content->getSize() : 0;
$rt = $content ? $content->getUltimateRedirectTarget() : null;
$this->mLatest, $revision->getContentModel() );
}
- wfProfileOut( __METHOD__ );
return $result;
}
return true;
}
- wfProfileIn( __METHOD__ );
if ( $isRedirect ) {
$this->insertRedirectEntry( $redirectTitle );
} else {
if ( $this->getTitle()->getNamespace() == NS_FILE ) {
RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
}
- wfProfileOut( __METHOD__ );
return ( $dbw->affectedRows() != 0 );
}
* @return bool
*/
public function updateIfNewerOn( $dbw, $revision ) {
- wfProfileIn( __METHOD__ );
$row = $dbw->selectRow(
array( 'revision', 'page' ),
if ( $row ) {
if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
- wfProfileOut( __METHOD__ );
return false;
}
$prev = $row->rev_id;
$ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
- wfProfileOut( __METHOD__ );
return $ret;
}
*/
public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
$edittime = null ) {
- wfProfileIn( __METHOD__ );
$baseRevId = null;
if ( $edittime && $sectionId !== 'new' ) {
}
}
- wfProfileOut( __METHOD__ );
return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
}
public function replaceSectionAtRev( $sectionId, Content $sectionContent,
$sectionTitle = '', $baseRevId = null
) {
- wfProfileIn( __METHOD__ );
if ( strval( $sectionId ) === '' ) {
// Whole-page edit; let the whole text through
$newContent = $sectionContent;
} else {
if ( !$this->supportsSections() ) {
- wfProfileOut( __METHOD__ );
throw new MWException( "sections not supported for content model " .
$this->getContentHandler()->getModelID() );
}
if ( !$rev ) {
wfDebug( __METHOD__ . " asked for bogus section (page: " .
$this->getId() . "; section: $sectionId)\n" );
- wfProfileOut( __METHOD__ );
return null;
}
if ( !$oldContent ) {
wfDebug( __METHOD__ . ": no page text\n" );
- wfProfileOut( __METHOD__ );
return null;
}
$newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
}
- wfProfileOut( __METHOD__ );
return $newContent;
}
* error will be returned. These two conditions are also possible with
* auto-detection due to MediaWiki's performance-optimised locking strategy.
*
- * @param bool|int $baseRevId The revision ID this edit was based off, if any
+ * @param bool|int $baseRevId The revision ID this edit was based off, if any.
+ * This is not the parent revision ID, rather the revision ID for older
+ * content used as the source for a rollback, for example.
* @param User $user The user doing the edit
*
* @throws MWException
* error will be returned. These two conditions are also possible with
* auto-detection due to MediaWiki's performance-optimised locking strategy.
*
- * @param bool|int $baseRevId The revision ID this edit was based off, if any
+ * @param bool|int $baseRevId The revision ID this edit was based off, if any.
+ * This is not the parent revision ID, rather the revision ID for older
+ * content used as the source for a rollback, for example.
* @param User $user The user doing the edit
* @param string $serialFormat Format for storing the content in the
* database.
throw new MWException( 'Something is trying to edit an article with an empty title' );
}
- wfProfileIn( __METHOD__ );
-
if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
- wfProfileOut( __METHOD__ );
return Status::newFatal( 'content-not-allowed-here',
ContentHandler::getLocalizedName( $content->getModel() ),
$this->getTitle()->getPrefixedText() );
$status->fatal( 'edit-hook-aborted' );
}
- wfProfileOut( __METHOD__ );
return $status;
}
wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
$status->fatal( 'edit-gone-missing' );
- wfProfileOut( __METHOD__ );
return $status;
} elseif ( !$old_content ) {
// Sanity check for bug 37225
- wfProfileOut( __METHOD__ );
throw new MWException( "Could not find text for current revision {$oldid}." );
}
$dbw->begin( __METHOD__ );
try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
$revisionId = $revision->insertOn( $dbw );
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
}
}
$user->incEditCount();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$dbw->rollback( __METHOD__ );
// Question: Would it perhaps be better if this method turned all
// exceptions into $status's?
$dbw->begin( __METHOD__ );
try {
- $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
+ $prepStatus = $content->prepareSave( $this, $flags, $oldid, $user );
$status->merge( $prepStatus );
if ( !$status->isOK() ) {
$dbw->rollback( __METHOD__ );
- wfProfileOut( __METHOD__ );
return $status;
}
$dbw->rollback( __METHOD__ );
$status->fatal( 'edit-already-exists' );
- wfProfileOut( __METHOD__ );
return $status;
}
}
$user->incEditCount();
- } catch ( MWException $e ) {
+ } catch ( Exception $e ) {
$dbw->rollback( __METHOD__ );
throw $e;
}
$user->addAutopromoteOnceGroups( 'onEdit' );
} );
- wfProfileOut( __METHOD__ );
return $status;
}
* Returns a stdClass with source, pst and output members
*
* @param Content $content
- * @param int|null $revid
+ * @param Revision|int|null $revision Revision object. For backwards compatibility, a
+ * revision ID is also accepted, but this is deprecated.
* @param User|null $user
* @param string|null $serialFormat
* @param bool $useCache Check shared prepared edit cache
* @since 1.21
*/
public function prepareContentForEdit(
- Content $content, $revid = null, User $user = null, $serialFormat = null, $useCache = true
+ Content $content, $revision = null, User $user = null, $serialFormat = null, $useCache = true
) {
- global $wgContLang, $wgUser;
+ global $wgContLang, $wgUser, $wgAjaxEditStash;
+
+ if ( is_object( $revision ) ) {
+ $revid = $revision->getId();
+ } else {
+ $revid = $revision;
+ // This code path is deprecated, and nothing is known to
+ // use it, so performance here shouldn't be a worry.
+ if ( $revid !== null ) {
+ $revision = Revision::newFromId( $revid, Revision::READ_LATEST );
+ } else {
+ $revision = null;
+ }
+ }
$user = is_null( $user ) ? $wgUser : $user;
//XXX: check $user->getId() here???
}
// The edit may have already been prepared via api.php?action=stashedit
- $cachedEdit = $useCache
+ $cachedEdit = $useCache && $wgAjaxEditStash
? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
: false;
if ( $cachedEdit ) {
$edit->output = $cachedEdit->output;
} else {
+ if ( $revision ) {
+ // We get here if vary-revision is set. This means that this page references
+ // itself (such as via self-transclusion). In this case, we need to make sure
+ // that any such self-references refer to the newly-saved revision, and not
+ // to the previous one, which could otherwise happen due to slave lag.
+ $oldCallback = $edit->popts->setCurrentRevisionCallback(
+ function ( $title, $parser = false ) use ( $revision, &$oldCallback ) {
+ if ( $title->equals( $revision->getTitle() ) ) {
+ return $revision;
+ } else {
+ return call_user_func(
+ $oldCallback,
+ $title,
+ $parser
+ );
+ }
+ }
+ );
+ }
$edit->output = $edit->pstContent
? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
: null;
* - changed: boolean, whether the revision changed the content (default true)
* - created: boolean, whether the revision created the page (default false)
* - moved: boolean, whether the page was moved (default false)
- * - oldcountable: boolean or null (default null):
+ * - oldcountable: boolean, null, or string 'no-change' (default null):
* - boolean: whether the page was counted as an article before that
* revision, only used in changed is true and created is false
- * - null: don't change the article count
+ * - null: if created is false, don't update the article count; if created
+ * is true, do update the article count
+ * - 'no-change': don't update the article count, ever
*/
public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
global $wgEnableParserCache;
- wfProfileIn( __METHOD__ );
-
$options += array(
'changed' => true,
'created' => false,
// already pre-save transformed once.
if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
- $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
+ $editInfo = $this->prepareContentForEdit( $content, $revision, $user );
} else {
wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
$editInfo = $this->mPreparedEdit;
Hooks::run( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
- if ( 0 == mt_rand( 0, 99 ) ) {
- // Flush old entries from the `recentchanges` table; we do this on
- // random requests so as to avoid an increase in writes for no good reason
- RecentChange::purgeExpiredChanges();
- }
+ // Flush old entries from the `recentchanges` table
+ JobQueueGroup::singleton()->push( RecentChangesUpdateJob::newPurgeJob() );
}
if ( !$this->exists() ) {
- wfProfileOut( __METHOD__ );
return;
}
$title = $this->mTitle->getPrefixedDBkey();
$shortTitle = $this->mTitle->getDBkey();
- if ( !$options['changed'] && !$options['moved'] ) {
+ if ( $options['oldcountable'] === 'no-change' ||
+ ( !$options['changed'] && !$options['moved'] )
+ ) {
$good = 0;
} elseif ( $options['created'] ) {
$good = (int)$this->isCountable( $editInfo );
self::onArticleEdit( $this->mTitle );
}
- wfProfileOut( __METHOD__ );
}
/**
public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
$serialFormat = null
) {
- wfProfileIn( __METHOD__ );
$serialized = $content->serialize( $serialFormat );
Hooks::run( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
- wfProfileOut( __METHOD__ );
}
/**
// Get the last edit not by this guy...
// Note: these may not be public values
- $user = intval( $current->getRawUser() );
- $user_text = $dbw->addQuotes( $current->getRawUserText() );
+ $user = intval( $current->getUser( Revision::RAW ) );
+ $user_text = $dbw->addQuotes( $current->getUserText( Revision::RAW ) );
$s = $dbw->selectRow( 'revision',
array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
array( 'rev_page' => $current->getPage(),
}
// Generate the edit summary if necessary
- $target = Revision::newFromId( $s->rev_id );
+ $target = Revision::newFromId( $s->rev_id, Revision::READ_LATEST );
if ( empty( $summary ) ) {
if ( $from == '' ) { // no public user name
$summary = wfMessage( 'revertpage-nouser' );
*
* @param Title $title
*/
- public static function onArticleCreate( $title ) {
+ public static function onArticleCreate( Title $title ) {
// Update existence markers on article/talk tabs...
- if ( $title->isTalkPage() ) {
- $other = $title->getSubjectPage();
- } else {
- $other = $title->getTalkPage();
- }
+ $other = $title->getOtherPage();
$other->invalidateCache();
$other->purgeSquid();
*
* @param Title $title
*/
- public static function onArticleDelete( $title ) {
+ public static function onArticleDelete( Title $title ) {
// Update existence markers on article/talk tabs...
- if ( $title->isTalkPage() ) {
- $other = $title->getSubjectPage();
- } else {
- $other = $title->getTalkPage();
- }
+ $other = $title->getOtherPage();
$other->invalidateCache();
$other->purgeSquid();
* Purge caches on page update etc
*
* @param Title $title
- * @todo Verify that $title is always a Title object (and never false or
- * null), add Title hint to parameter $title.
*/
- public static function onArticleEdit( $title ) {
+ public static function onArticleEdit( Title $title ) {
// Invalidate caches of articles which include this page
DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
}
/**
- * Updates cascading protections
+ * Opportunistically enqueue link update jobs given fresh parser output if useful
*
- * @param ParserOutput $parserOutput ParserOutput object for the current version
+ * @param ParserOutput $parserOutput Current version page output
+ * @return bool Whether a job was pushed
+ * @since 1.25
*/
- public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
- if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
- return;
- }
-
- // templatelinks or imagelinks tables may have become out of sync,
- // especially if using variable-based transclusions.
- // For paranoia, check if things have changed and if
- // so apply updates to the database. This will ensure
- // that cascaded protections apply as soon as the changes
- // are visible.
-
- // Get templates from templatelinks and images from imagelinks
- $id = $this->getId();
-
- $dbLinks = array();
-
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'templatelinks' ),
- array( 'tl_namespace', 'tl_title' ),
- array( 'tl_from' => $id ),
- __METHOD__
- );
-
- foreach ( $res as $row ) {
- $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
+ public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
+ if ( wfReadOnly() ) {
+ return false;
}
- $dbr = wfGetDB( DB_SLAVE );
- $res = $dbr->select( array( 'imagelinks' ),
- array( 'il_to' ),
- array( 'il_from' => $id ),
- __METHOD__
- );
-
- foreach ( $res as $row ) {
- $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
+ if ( $this->mTitle->areRestrictionsCascading() ) {
+ // If the page is cascade protecting, the links should really be up-to-date
+ $params = array( 'prioritize' => true );
+ } elseif ( $parserOutput->hasDynamicContent() ) {
+ // Assume the output contains time/random based magic words
+ $params = array();
+ } else {
+ // If the inclusions are deterministic, the edit-triggered link jobs are enough
+ return false;
}
- // Get templates and images from parser output.
- $poLinks = array();
- foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
- foreach ( $templates as $dbk => $id ) {
- $poLinks["$ns:$dbk"] = true;
- }
- }
- foreach ( $parserOutput->getImages() as $dbk => $id ) {
- $poLinks[NS_FILE . ":$dbk"] = true;
+ // Check if the last link refresh was before page_touched
+ if ( $this->getLinksTimestamp() < $this->getTouched() ) {
+ JobQueueGroup::singleton()->push( EnqueueJob::newFromLocalJobs(
+ new JobSpecification( 'refreshLinks', $params, array(), $this->mTitle )
+ ) );
+ return true;
}
- // Get the diff
- $links_diff = array_diff_key( $poLinks, $dbLinks );
-
- if ( count( $links_diff ) > 0 ) {
- // Whee, link updates time.
- // Note: we are only interested in links here. We don't need to get
- // other DataUpdate items from the parser output.
- $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
- $u->doUpdate();
- }
+ return false;
}
/**