private $config;
/**
- * @var String Cache what action this request is
+ * @var string Cache what action this request is
*/
private $action;
*/
private static function getUrlDomainDistance( $url ) {
$clusterWiki = WikiMap::getWikiFromUrl( $url );
- if ( $clusterWiki === wfWikiID() ) {
+ if ( WikiMap::isCurrentWikiId( $clusterWiki ) ) {
return 'local'; // the current wiki
- } elseif ( $clusterWiki !== false ) {
+ }
+ if ( $clusterWiki !== false ) {
return 'remote'; // another wiki in this cluster/farm
}
* @since 1.26
*/
public function doPostOutputShutdown( $mode = 'normal' ) {
+ // Record backend request timing
+ $timing = $this->context->getTiming();
+ $timing->mark( 'requestShutdown' );
+
// Perform the last synchronous operations...
try {
- // Record backend request timing
- $timing = $this->context->getTiming();
- $timing->mark( 'requestShutdown' );
// Show visible profiling data if enabled (which cannot be post-send)
Profiler::instance()->logDataPageOutputOnly();
} catch ( Exception $e ) {
__METHOD__
);
- // Important: this must be the last deferred update added (T100085, T154425)
- DeferredUpdates::addCallableUpdate( [ JobQueueGroup::class, 'pushLazyJobs' ] );
-
// Do any deferred jobs; preferring to run them now if a client will not wait on them
DeferredUpdates::doUpdates( $blocksHttpClient ? 'enqueue' : 'run' );
/** @var array Map of (bucket => (queue => JobQueue, types => list of types) */
protected $coalescedQueues;
- /** @var Job[] */
- protected $bufferedJobs = [];
-
const TYPE_DEFAULT = 1; // integer; jobs popped by default
const TYPE_ANY = 2; // integer; any job
self::$instances[$wiki] = new self( $wiki, wfConfiguredReadOnlyReason() );
// Make sure jobs are not getting pushed to bogus wikis. This can confuse
// the job runner system into spawning endless RPC requests that fail (T171371).
- if ( $wiki !== wfWikiID() && !in_array( $wiki, $wgLocalDatabases ) ) {
+ if ( !WikiMap::isCurrentWikiId( $wiki ) && !in_array( $wiki, $wgLocalDatabases ) ) {
self::$instances[$wiki]->invalidWiki = true;
}
}
global $wgJobTypeConf;
$conf = [ 'wiki' => $this->wiki, 'type' => $type ];
- if ( isset( $wgJobTypeConf[$type] ) ) {
- $conf = $conf + $wgJobTypeConf[$type];
- } else {
- $conf = $conf + $wgJobTypeConf['default'];
- }
+ $conf += $wgJobTypeConf[$type] ?? $wgJobTypeConf['default'];
$conf['aggregator'] = JobQueueAggregator::singleton();
if ( !isset( $conf['readOnlyReason'] ) ) {
$conf['readOnlyReason'] = $this->readOnlyReason;
// Throw errors now instead of on push(), when other jobs may be buffered
$this->assertValidJobs( $jobs );
- $this->bufferedJobs = array_merge( $this->bufferedJobs, $jobs );
+ DeferredUpdates::addUpdate( new JobQueueEnqueueUpdate( $this->wiki, $jobs ) );
}
/**
*
* @return void
* @since 1.26
+ * @deprecated Since 1.33 Not needed anymore
*/
public static function pushLazyJobs() {
- foreach ( self::$instances as $group ) {
- try {
- $group->push( $group->bufferedJobs );
- $group->bufferedJobs = [];
- } catch ( Exception $e ) {
- // Get in as many jobs as possible and let other post-send updates happen
- MWExceptionHandler::logException( $e );
- }
- }
+ wfDeprecated( __METHOD__, '1.33' );
}
/**
*/
private function getCachedConfigVar( $name ) {
// @TODO: cleanup this whole method with a proper config system
- if ( $this->wiki === wfWikiID() ) {
+ if ( WikiMap::isCurrentWikiId( $this->wiki ) ) {
return $GLOBALS[$name]; // common case
} else {
$wiki = $this->wiki;
}
}
}
-
- function __destruct() {
- $n = count( $this->bufferedJobs );
- if ( $n > 0 ) {
- $type = implode( ', ', array_unique( array_map( 'get_class', $this->bufferedJobs ) ) );
- trigger_error( __METHOD__ . ": $n buffered job(s) of type(s) $type never inserted." );
- }
- }
}
static function makeMsgId() {
global $wgSMTP, $wgServer;
- $msgid = uniqid( wfWikiID() . ".", true ); /* true required for cygwin */
+ $domainId = WikiMap::getCurrentWikiDomain()->getId();
+ $msgid = uniqid( $domainId . ".", true /** for cygwin */ );
if ( is_array( $wgSMTP ) && isset( $wgSMTP['IDHost'] ) && $wgSMTP['IDHost'] ) {
$domain = $wgSMTP['IDHost'];
} else {
try {
foreach ( $to as $recip ) {
$sent = mail(
- $recip,
+ $recip->toString(),
self::quotedPrintable( $subject ),
$body,
$headers,
$dbw->startAtomic( __METHOD__ );
if ( !$oldLatest || $oldLatest == $this->lockAndGetLatest() ) {
+ $contLang = MediaWikiServices::getInstance()->getContentLanguage();
+ $truncatedFragment = $contLang->truncateForDatabase( $rt->getFragment(), 255 );
$dbw->upsert(
'redirect',
[
'rd_from' => $this->getId(),
'rd_namespace' => $rt->getNamespace(),
'rd_title' => $rt->getDBkey(),
- 'rd_fragment' => $rt->getFragment(),
+ 'rd_fragment' => $truncatedFragment,
'rd_interwiki' => $rt->getInterwiki(),
],
[ 'rd_from' ],
[
'rd_namespace' => $rt->getNamespace(),
'rd_title' => $rt->getDBkey(),
- 'rd_fragment' => $rt->getFragment(),
+ 'rd_fragment' => $truncatedFragment,
'rd_interwiki' => $rt->getInterwiki(),
],
__METHOD__
// in the job queue to avoid simultaneous deletion operations would add overhead.
// Number of archived revisions cannot be known beforehand, because edits can be made
// while deletion operations are being processed, changing the number of archivals.
- $archivedRevisionCount = $dbw->selectRowCount(
- 'archive', '1', [ 'ar_page_id' => $id ], __METHOD__
+ $archivedRevisionCount = $dbw->selectField(
+ 'archive', 'COUNT(*)',
+ [
+ 'ar_namespace' => $this->getTitle()->getNamespace(),
+ 'ar_title' => $this->getTitle()->getDBkey(),
+ 'ar_page_id' => $id
+ ], __METHOD__
);
// Clone the title and wikiPage, so we have the information we need when
// Do not include the namespace since there can be multiple aliases to it
// due to different namespace text definitions on different wikis. This only
// means that some cache invalidations happen that are not strictly needed.
- $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+ $cache->makeGlobalKey(
+ 'interwiki-page',
+ WikiMap::getCurrentWikiDomain()->getId(),
+ $title->getDBkey()
+ )
);
} );
}
* @return Title
*/
public function getRelevantTitle() {
- if ( isset( $this->mRelevantTitle ) ) {
- return $this->mRelevantTitle;
- }
- return $this->getTitle();
+ return $this->mRelevantTitle ?? $this->getTitle();
}
/**
return $newMessagesAlert;
}
- if ( count( $newtalks ) == 1 && $newtalks[0]['wiki'] === wfWikiID() ) {
+ if ( count( $newtalks ) == 1 && WikiMap::isCurrentWikiId( $newtalks[0]['wiki'] ) ) {
$uTalkTitle = $user->getTalkPage();
$lastSeenRev = $newtalks[0]['rev'] ?? null;
$nofAuthors = 0;
* @param string $section The designation of the section being pointed to,
* to be included in the link, like "§ion=$section"
* @param string|null $tooltip The tooltip to use for the link: will be escaped
- * and wrapped in the 'editsectionhint' message.
- * Not setting this parameter is deprecated.
- * @param Language|string $lang Language object or language code string.
- * Type string is deprecated. Not setting this parameter is deprecated.
+ * and wrapped in the 'editsectionhint' message
+ * @param Language $lang Language object
* @return string HTML to use for edit link
*/
- public function doEditSectionLink( Title $nt, $section, $tooltip = null, $lang = false ) {
+ public function doEditSectionLink( Title $nt, $section, $tooltip, Language $lang ) {
// HTML generated here should probably have userlangattributes
// added to it for LTR text on RTL pages
- if ( !$lang instanceof Language ) {
- wfDeprecated( __METHOD__ . ' with other type than Language for $lang', '1.32' );
- $lang = wfGetLangObj( $lang );
- }
-
$attribs = [];
if ( !is_null( $tooltip ) ) {
$attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
// save settings
if ( !$fetchedStatus->isOK() ) {
- $this->getOutput()->addWikiText( $fetchedStatus->getWikiText() );
+ $this->getOutput()->addWikiTextAsInterface( $fetchedStatus->getWikiText() );
return;
}
return;
} else {
// Print an error message and redisplay the form
- $out->addWikiText( '<div class="error">' . $status->getWikiText() . '</div>' );
+ $out->wrapWikiTextAsInterface( 'error', $status->getWikiText() );
}
}
}
function editUserGroupsForm( $username ) {
$status = $this->fetchUser( $username, true );
if ( !$status->isOK() ) {
- $this->getOutput()->addWikiText( $status->getWikiText() );
+ $this->getOutput()->addWikiTextAsInterface( $status->getWikiText() );
return;
} else {
$parts = explode( $this->getConfig()->get( 'UserrightsInterwikiDelimiter' ), $username );
if ( count( $parts ) < 2 ) {
$name = trim( $username );
- $database = '';
+ $wikiId = '';
} else {
- list( $name, $database ) = array_map( 'trim', $parts );
+ list( $name, $wikiId ) = array_map( 'trim', $parts );
- if ( $database == wfWikiID() ) {
- $database = '';
+ if ( WikiMap::isCurrentWikiId( $wikiId ) ) {
+ $wikiId = '';
} else {
if ( $writing && !$this->getUser()->isAllowed( 'userrights-interwiki' ) ) {
return Status::newFatal( 'userrights-no-interwiki' );
}
- if ( !UserRightsProxy::validDatabase( $database ) ) {
- return Status::newFatal( 'userrights-nodatabase', $database );
+ if ( !UserRightsProxy::validDatabase( $wikiId ) ) {
+ return Status::newFatal( 'userrights-nodatabase', $wikiId );
}
}
}
// We'll do a lookup for the name internally.
$id = intval( substr( $name, 1 ) );
- if ( $database == '' ) {
+ if ( $wikiId == '' ) {
$name = User::whoIs( $id );
} else {
- $name = UserRightsProxy::whoIs( $database, $id );
+ $name = UserRightsProxy::whoIs( $wikiId, $id );
}
if ( !$name ) {
}
}
- if ( $database == '' ) {
+ if ( $wikiId == '' ) {
$user = User::newFromName( $name );
} else {
- $user = UserRightsProxy::newFromName( $database, $name );
+ $user = UserRightsProxy::newFromName( $wikiId, $name );
}
if ( !$user || $user->isAnon() ) {
* @param UserGroupMembership[] $usergroups Associative array of (group name as string =>
* UserGroupMembership object) for groups the user belongs to
* @param User $user
- * @return Array with 2 elements: the XHTML table element with checkxboes, and
+ * @return array Array with 2 elements: the XHTML table element with checkxboes, and
* whether any groups are changeable
*/
private function groupCheckboxes( $usergroups, $user ) {
*/
public static function getDefaultOption( $opt ) {
$defOpts = self::getDefaultOptions();
- if ( isset( $defOpts[$opt] ) ) {
- return $defOpts[$opt];
- } else {
- return null;
- }
+ return $defOpts[$opt] ?? null;
}
/**
* Check if user is blocked from editing a particular article
*
* @param Title $title Title to check
- * @param bool $bFromSlave Whether to check the replica DB instead of the master
+ * @param bool $fromSlave Whether to check the replica DB instead of the master
* @return bool
*/
- public function isBlockedFrom( $title, $bFromSlave = false ) {
- global $wgBlockAllowsUTEdit;
+ public function isBlockedFrom( $title, $fromSlave = false ) {
+ $blocked = $this->isHidden();
- $blocked = $this->isBlocked( $bFromSlave );
- $allowUsertalk = ( $wgBlockAllowsUTEdit ? $this->mAllowUsertalk : false );
- // If a user's name is suppressed, they cannot make edits anywhere
- if ( !$this->mHideName && $allowUsertalk && $title->getText() === $this->getName()
- && $title->getNamespace() == NS_USER_TALK ) {
- $blocked = false;
- wfDebug( __METHOD__ . ": self-talk page, ignoring any blocks\n" );
+ if ( !$blocked ) {
+ $block = $this->getBlock( $fromSlave );
+ if ( $block ) {
+ $blocked = $block->preventsEdit( $title );
+ }
}
+ // only for the purpose of the hook. We really don't need this here.
+ $allowUsertalk = $this->mAllowUsertalk;
+
Hooks::run( 'UserIsBlockedFrom', [ $this, $title, &$blocked, &$allowUsertalk ] );
return $blocked;
*/
public function isHidden() {
if ( $this->mHideName !== null ) {
- return $this->mHideName;
+ return (bool)$this->mHideName;
}
$this->getBlockedStatus();
if ( !$this->mHideName ) {
$this->mHideName = $authUser && $authUser->isHidden();
Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
}
- return $this->mHideName;
+ return (bool)$this->mHideName;
}
/**
// and it is always for the same wiki, but we double-check here in
// case that changes some time in the future.
if ( count( $newMessageLinks ) === 1
- && $newMessageLinks[0]['wiki'] === wfWikiID()
+ && WikiMap::isCurrentWikiId( $newMessageLinks[0]['wiki'] )
&& $newMessageLinks[0]['rev']
) {
/** @var Revision $newMessageRevision */
if ( $count === null ) {
// it has not been initialized. do so.
- $count = $this->initEditCount();
+ $count = $this->initEditCountInternal();
}
$this->mEditCount = $count;
}
return $this->mBlock && $this->mBlock->prevents( 'sendemail' );
}
+ /**
+ * Get whether the user is blocked from using Special:Upload
+ *
+ * @return bool
+ */
+ public function isBlockedFromUpload() {
+ $this->getBlockedStatus();
+ return $this->mBlock && $this->mBlock->prevents( 'upload' );
+ }
+
/**
* Get whether the user is allowed to create an account.
* @return bool
}
/**
- * Deferred version of incEditCountImmediate()
- *
- * This function, rather than incEditCountImmediate(), should be used for
- * most cases as it avoids potential deadlocks caused by concurrent editing.
+ * Schedule a deferred update to update the user's edit count
*/
public function incEditCount() {
- wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
- function () {
- $this->incEditCountImmediate();
- },
- __METHOD__
+ if ( $this->isAnon() ) {
+ return; // sanity
+ }
+
+ DeferredUpdates::addUpdate(
+ new UserEditCountUpdate( $this, 1 ),
+ DeferredUpdates::POSTSEND
);
}
/**
- * Increment the user's edit-count field.
- * Will have no effect for anonymous users.
- * @since 1.26
+ * This method should not be called outside User/UserEditCountUpdate
+ *
+ * @param int $count
*/
- public function incEditCountImmediate() {
- if ( $this->isAnon() ) {
- return;
- }
-
- $dbw = wfGetDB( DB_MASTER );
- // No rows will be "affected" if user_editcount is NULL
- $dbw->update(
- 'user',
- [ 'user_editcount=user_editcount+1' ],
- [ 'user_id' => $this->getId(), 'user_editcount IS NOT NULL' ],
- __METHOD__
- );
- // Lazy initialization check...
- if ( $dbw->affectedRows() == 0 ) {
- // Now here's a goddamn hack...
- $dbr = wfGetDB( DB_REPLICA );
- if ( $dbr !== $dbw ) {
- // If we actually have a replica DB server, the count is
- // at least one behind because the current transaction
- // has not been committed and replicated.
- $this->mEditCount = $this->initEditCount( 1 );
- } else {
- // But if DB_REPLICA is selecting the master, then the
- // count we just read includes the revision that was
- // just added in the working transaction.
- $this->mEditCount = $this->initEditCount();
- }
- } else {
- if ( $this->mEditCount === null ) {
- $this->getEditCount();
- $dbr = wfGetDB( DB_REPLICA );
- $this->mEditCount += ( $dbr !== $dbw ) ? 1 : 0;
- } else {
- $this->mEditCount++;
- }
- }
- // Edit count in user cache too
- $this->invalidateCache();
+ public function setEditCountInternal( $count ) {
+ $this->mEditCount = $count;
}
/**
* Initialize user_editcount from data out of the revision table
*
- * @param int $add Edits to add to the count from the revision table
+ * This method should not be called outside User/UserEditCountUpdate
+ *
* @return int Number of edits
*/
- protected function initEditCount( $add = 0 ) {
+ public function initEditCountInternal() {
// Pull from a replica DB to be less cruel to servers
// Accuracy isn't the point anyway here
$dbr = wfGetDB( DB_REPLICA );
[],
$actorWhere['joins']
);
- $count = $count + $add;
$dbw = wfGetDB( DB_MASTER );
$dbw->update(
'user',
[ 'user_editcount' => $count ],
- [ 'user_id' => $this->getId() ],
+ [
+ 'user_id' => $this->getId(),
+ 'user_editcount IS NULL OR user_editcount < ' . (int)$count
+ ],
__METHOD__
);