Merge "mediawiki.user: Minor clean up of code and unit tests"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Fri, 16 Sep 2016 01:34:01 +0000 (01:34 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Fri, 16 Sep 2016 01:34:01 +0000 (01:34 +0000)
58 files changed:
RELEASE-NOTES-1.28
autoload.php
docs/hooks.txt
includes/Defines.php
includes/EditPage.php
includes/MediaWiki.php
includes/Title.php
includes/api/ApiEditPage.php
includes/auth/EmailNotificationSecondaryAuthenticationProvider.php
includes/auth/TemporaryPasswordPrimaryAuthenticationProvider.php
includes/cache/MessageCache.php
includes/changes/RecentChange.php
includes/db/Database.php
includes/db/DatabaseMssql.php
includes/db/loadbalancer/LBFactoryMW.php
includes/deferred/AtomicSectionUpdate.php
includes/deferred/AutoCommitUpdate.php
includes/deferred/LinksUpdate.php
includes/deferred/MWCallableUpdate.php
includes/filerepo/LocalRepo.php
includes/filerepo/file/LocalFile.php
includes/jobqueue/JobQueueDB.php
includes/jobqueue/jobs/RecentChangesUpdateJob.php
includes/jobqueue/utils/PurgeJobUtils.php
includes/libs/rdbms/database/DBConnRef.php
includes/libs/rdbms/database/IDatabase.php
includes/libs/rdbms/defines.php [new file with mode: 0644]
includes/libs/rdbms/lbfactory/LBFactory.php
includes/libs/rdbms/loadbalancer/LoadBalancer.php
includes/libs/rdbms/loadmonitor/ILoadMonitor.php [new file with mode: 0644]
includes/libs/rdbms/loadmonitor/LoadMonitor.php
includes/libs/rdbms/loadmonitor/LoadMonitorMySQL.php
includes/libs/rdbms/loadmonitor/LoadMonitorNull.php
includes/page/WikiPage.php
includes/resourceloader/ResourceLoaderContext.php
includes/resourceloader/ResourceLoaderModule.php
includes/revisiondelete/RevDelList.php
includes/search/AugmentPageProps.php [new file with mode: 0644]
includes/search/PerRowAugmentor.php [new file with mode: 0644]
includes/search/ResultAugmentor.php [new file with mode: 0644]
includes/search/ResultSetAugmentor.php [new file with mode: 0644]
includes/search/SearchEngine.php
includes/search/SearchNearMatchResultSet.php
includes/search/SearchResult.php
includes/search/SearchResultSet.php
includes/search/SqlSearchResultSet.php
includes/specials/SpecialSearch.php
includes/user/User.php
includes/user/UserRightsProxy.php
maintenance/Maintenance.php
maintenance/doMaintenance.php
resources/Resources.php
tests/phpunit/includes/api/ApiQueryAllPagesTest.php
tests/phpunit/includes/api/query/ApiQueryRevisionsTest.php
tests/phpunit/includes/db/DatabaseTest.php
tests/phpunit/includes/page/WikiPageTest.php
tests/phpunit/includes/search/SearchEngineTest.php
tests/phpunit/includes/user/UserTest.php

index 0b86bc2..c3a91c4 100644 (file)
@@ -171,6 +171,7 @@ changes to languages because of Phabricator reports.
   phpunit.php: --regex and --keep-uploads. Instead of --regex, use --filter.
   Instead of --keep-uploads, use the same option to parserTests.php, but you
   must specify a directory with --upload-dir.
+* The 'jquery.arrowSteps' ResourceLoader module is now deprecated.
 
 == Compatibility ==
 
index 96c8190..1cb71d8 100644 (file)
@@ -153,6 +153,7 @@ $wgAutoloadLocalClasses = [
        'AtomFeed' => __DIR__ . '/includes/Feed.php',
        'AtomicSectionUpdate' => __DIR__ . '/includes/deferred/AtomicSectionUpdate.php',
        'AttachLatest' => __DIR__ . '/maintenance/attachLatest.php',
+       'AugmentPageProps' => __DIR__ . '/includes/search/AugmentPageProps.php',
        'AuthManagerSpecialPage' => __DIR__ . '/includes/specialpage/AuthManagerSpecialPage.php',
        'AuthPlugin' => __DIR__ . '/includes/AuthPlugin.php',
        'AuthPluginUser' => __DIR__ . '/includes/AuthPlugin.php',
@@ -582,6 +583,7 @@ $wgAutoloadLocalClasses = [
        'IExpiringStore' => __DIR__ . '/includes/libs/objectcache/IExpiringStore.php',
        'IJobSpecification' => __DIR__ . '/includes/jobqueue/JobSpecification.php',
        'ILoadBalancer' => __DIR__ . '/includes/libs/rdbms/loadbalancer/ILoadBalancer.php',
+       'ILoadMonitor' => __DIR__ . '/includes/libs/rdbms/loadmonitor/ILoadMonitor.php',
        'IP' => __DIR__ . '/includes/utils/IP.php',
        'IPSet' => __DIR__ . '/includes/compat/IPSetCompat.php',
        'IPTC' => __DIR__ . '/includes/media/IPTC.php',
@@ -1043,6 +1045,7 @@ $wgAutoloadLocalClasses = [
        'PatrolLog' => __DIR__ . '/includes/logging/PatrolLog.php',
        'PatrolLogFormatter' => __DIR__ . '/includes/logging/PatrolLogFormatter.php',
        'Pbkdf2Password' => __DIR__ . '/includes/password/Pbkdf2Password.php',
+       'PerRowAugmentor' => __DIR__ . '/includes/search/PerRowAugmentor.php',
        'PermissionsError' => __DIR__ . '/includes/exception/PermissionsError.php',
        'PhpHttpRequest' => __DIR__ . '/includes/HttpFunctions.php',
        'PhpXmlBugTester' => __DIR__ . '/includes/installer/PhpBugTests.php',
@@ -1182,6 +1185,8 @@ $wgAutoloadLocalClasses = [
        'ResourceLoaderUserTokensModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderUserTokensModule.php',
        'ResourceLoaderWikiModule' => __DIR__ . '/includes/resourceloader/ResourceLoaderWikiModule.php',
        'RestbaseVirtualRESTService' => __DIR__ . '/includes/libs/virtualrest/RestbaseVirtualRESTService.php',
+       'ResultAugmentor' => __DIR__ . '/includes/search/ResultAugmentor.php',
+       'ResultSetAugmentor' => __DIR__ . '/includes/search/ResultSetAugmentor.php',
        'ResultWrapper' => __DIR__ . '/includes/libs/rdbms/database/resultwrapper/ResultWrapper.php',
        'RevDelArchiveItem' => __DIR__ . '/includes/revisiondelete/RevDelArchiveItem.php',
        'RevDelArchiveList' => __DIR__ . '/includes/revisiondelete/RevDelArchiveList.php',
index a7fb873..ae0770b 100644 (file)
@@ -2699,6 +2699,13 @@ $page: WikiPage that is being indexed
 $output: ParserOutput that is produced from the page
 $engine: SearchEngine for which the indexing is intended
 
+'SearchResultsAugment': Allows extension to add its code to the list of search
+result augmentors.
+&$setAugmentors: List of whole-set augmentor objects, must implement ResultSetAugmentor
+&$rowAugmentors: List of per-row augmentor objects, must implement ResultAugmentor.
+Note that lists should be in the format name => object and the names in both lists should
+be distinct.
+
 'SecondaryDataUpdates': Allows modification of the list of DataUpdates to
 perform when page content is modified. Currently called by
 AbstractContent::getSecondaryDataUpdates.
index ab02a8e..077f39a 100644 (file)
  * @defgroup Constants MediaWiki constants
  */
 
-/**@{
- * Database related constants
- */
-define( 'DBO_DEBUG', 1 );
-define( 'DBO_NOBUFFER', 2 );
-define( 'DBO_IGNORE', 4 );
-define( 'DBO_TRX', 8 ); // automatically start transaction on first query
-define( 'DBO_DEFAULT', 16 );
-define( 'DBO_PERSISTENT', 32 );
-define( 'DBO_SYSDBA', 64 ); // for oracle maintenance
-define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
-define( 'DBO_SSL', 256 );
-define( 'DBO_COMPRESS', 512 );
-/**@}*/
-
-/**@{
- * Valid database indexes
- * Operation-based indexes
- */
-define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
-define( 'DB_MASTER', -2 );    # Write to master (or only server)
-/**@}*/
-
 # Obsolete aliases
 define( 'DB_SLAVE', -1 );
 
@@ -185,16 +162,10 @@ define( 'EDIT_AUTOSUMMARY', 64 );
 define( 'EDIT_INTERNAL', 128 );
 /**@}*/
 
-/**@{
- * Flags for Database::makeList()
- * These are also available as Database class constants
+/**
+ * Database related
  */
-define( 'LIST_COMMA', 0 );
-define( 'LIST_AND', 1 );
-define( 'LIST_SET', 2 );
-define( 'LIST_NAMES', 3 );
-define( 'LIST_OR', 4 );
-/**@}*/
+require_once __DIR__ . '/libs/rdbms/defines.php';
 
 /**
  * Unicode and normalisation related
index 9d329e8..606b4cd 100644 (file)
@@ -578,7 +578,7 @@ class EditPage {
                        ) {
                                $this->displayViewSourcePage(
                                        $this->getContentObject(),
-                                       wfMessage(
+                                       $this->context->msg(
                                                'contentmodelediterror',
                                                $revision->getContentModel(),
                                                $this->contentModel
@@ -715,7 +715,7 @@ class EditPage {
                Hooks::run( 'EditPage::showReadOnlyForm:initial', [ $this, &$wgOut ] );
 
                $wgOut->setRobotPolicy( 'noindex,nofollow' );
-               $wgOut->setPageTitle( wfMessage(
+               $wgOut->setPageTitle( $this->context->msg(
                        'viewsource-title',
                        $this->getContextTitle()->getPrefixedText()
                ) );
@@ -1178,12 +1178,12 @@ class EditPage {
                                                                if ( $firstrev && $firstrev->getId() == $undo ) {
                                                                        $userText = $undorev->getUserText();
                                                                        if ( $userText === '' ) {
-                                                                               $undoSummary = wfMessage(
+                                                                               $undoSummary = $this->context->msg(
                                                                                        'undo-summary-username-hidden',
                                                                                        $undo
                                                                                )->inContentLanguage()->text();
                                                                        } else {
-                                                                               $undoSummary = wfMessage(
+                                                                               $undoSummary = $this->context->msg(
                                                                                        'undo-summary',
                                                                                        $undo,
                                                                                        $userText
@@ -1192,7 +1192,7 @@ class EditPage {
                                                                        if ( $this->summary === '' ) {
                                                                                $this->summary = $undoSummary;
                                                                        } else {
-                                                                               $this->summary = $undoSummary . wfMessage( 'colon-separator' )
+                                                                               $this->summary = $undoSummary . $this->context->msg( 'colon-separator' )
                                                                                        ->inContentLanguage()->text() . $this->summary;
                                                                        }
                                                                        $this->undidRev = $undo;
@@ -1210,7 +1210,7 @@ class EditPage {
                                        // Messages: undo-success, undo-failure, undo-norev, undo-nochange
                                        $class = ( $undoMsg == 'success' ? '' : 'error ' ) . "mw-undo-{$undoMsg}";
                                        $this->editFormPageTop .= $wgOut->parse( "<div class=\"{$class}\">" .
-                                               wfMessage( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
+                                               $this->context->msg( 'undo-' . $undoMsg )->plain() . '</div>', true, /* interface */true );
                                }
 
                                if ( $content === false ) {
@@ -1674,7 +1674,7 @@ class EditPage {
                        // passed.
                        if ( $this->summary === '' ) {
                                $cleanSectionTitle = $wgParser->stripSectionName( $this->sectiontitle );
-                               return wfMessage( 'newsectionsummary' )
+                               return $this->context->msg( 'newsectionsummary' )
                                        ->rawParams( $cleanSectionTitle )->inContentLanguage()->text();
                        }
                } elseif ( $this->summary !== '' ) {
@@ -1682,7 +1682,7 @@ class EditPage {
                        # This is a new section, so create a link to the new section
                        # in the revision summary.
                        $cleanSummary = $wgParser->stripSectionName( $this->summary );
-                       return wfMessage( 'newsectionsummary' )
+                       return $this->context->msg( 'newsectionsummary' )
                                ->rawParams( $cleanSummary )->inContentLanguage()->text();
                }
                return $this->summary;
@@ -2357,7 +2357,7 @@ class EditPage {
                if ( $displayTitle === false ) {
                        $displayTitle = $contextTitle->getPrefixedText();
                }
-               $wgOut->setPageTitle( wfMessage( $msg, $displayTitle ) );
+               $wgOut->setPageTitle( $this->context->msg( $msg, $displayTitle ) );
                # Transmit the name of the message to JavaScript for live preview
                # Keep Resources.php/mediawiki.action.edit.preview in sync with the possible keys
                $wgOut->addJsConfigVars( [
@@ -2442,7 +2442,7 @@ class EditPage {
                # Try to add a custom edit intro, or use the standard one if this is not possible.
                if ( !$this->showCustomIntro() && !$this->mTitle->exists() ) {
                        $helpLink = wfExpandUrl( Skin::makeInternalOrExternalUrl(
-                               wfMessage( 'helppage' )->inContentLanguage()->text()
+                               $this->context->msg( 'helppage' )->inContentLanguage()->text()
                        ) );
                        if ( $wgUser->isLoggedIn() ) {
                                $wgOut->wrapWikiMsg(
@@ -2632,7 +2632,7 @@ class EditPage {
                        . Html::rawElement(
                                'label',
                                [ 'for' => 'wpAntispam' ],
-                               wfMessage( 'simpleantispam-label' )->parse()
+                               $this->context->msg( 'simpleantispam-label' )->parse()
                        )
                        . Xml::element(
                                'input',
@@ -2662,8 +2662,8 @@ class EditPage {
                                : 'confirmrecreate';
                        $wgOut->addHTML(
                                '<div class="mw-confirm-recreate">' .
-                                       wfMessage( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
-                               Xml::checkLabel( wfMessage( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
+                                       $this->context->msg( $key, $username, "<nowiki>$comment</nowiki>" )->parse() .
+                               Xml::checkLabel( $this->context->msg( 'recreate' )->text(), 'wpRecreate', 'wpRecreate', false,
                                        [ 'title' => Linker::titleAttrib( 'recreate' ), 'tabindex' => 1, 'id' => 'wpRecreate' ]
                                ) .
                                '</div>'
@@ -2768,7 +2768,7 @@ class EditPage {
                                $this->showConflict();
                        } catch ( MWContentSerializationException $ex ) {
                                // this can't really happen, but be nice if it does.
-                               $msg = wfMessage(
+                               $msg = $this->context->msg(
                                        'content-failed-to-parse',
                                        $this->contentModel,
                                        $this->contentFormat,
@@ -2859,7 +2859,7 @@ class EditPage {
                if ( count( $editNotices ) ) {
                        $wgOut->addHTML( implode( "\n", $editNotices ) );
                } else {
-                       $msg = wfMessage( 'editnotice-notext' );
+                       $msg = $this->context->msg( 'editnotice-notext' );
                        if ( !$msg->isDisabled() ) {
                                $wgOut->addHTML(
                                        '<div class="mw-editnotice-notext">'
@@ -3054,7 +3054,7 @@ class EditPage {
                                ]
                        );
                } else {
-                       if ( !wfMessage( 'longpage-hint' )->isDisabled() ) {
+                       if ( !$this->context->msg( 'longpage-hint' )->isDisabled() ) {
                                $wgOut->wrapWikiMsg( "<div id='mw-edit-longpage-hint'>\n$1\n</div>",
                                        [
                                                'longpage-hint',
@@ -3135,7 +3135,7 @@ class EditPage {
                                return;
                        }
                }
-               $labelText = wfMessage( $isSubjectPreview ? 'subject' : 'summary' )->parse();
+               $labelText = $this->context->msg( $isSubjectPreview ? 'subject' : 'summary' )->parse();
                list( $label, $input ) = $this->getSummaryInput(
                        $summary,
                        $labelText,
@@ -3162,13 +3162,14 @@ class EditPage {
                global $wgParser;
 
                if ( $isSubjectPreview ) {
-                       $summary = wfMessage( 'newsectionsummary' )->rawParams( $wgParser->stripSectionName( $summary ) )
+                       $summary = $this->context->msg( 'newsectionsummary' )
+                               ->rawParams( $wgParser->stripSectionName( $summary ) )
                                ->inContentLanguage()->text();
                }
 
                $message = $isSubjectPreview ? 'subject-preview' : 'summary-preview';
 
-               $summary = wfMessage( $message )->parse()
+               $summary = $this->context->msg( $message )->parse()
                        . Linker::commentBlock( $summary, $this->mTitle, $isSubjectPreview );
                return Xml::tags( 'div', [ 'class' => 'mw-summary-preview' ], $summary );
        }
@@ -3333,7 +3334,7 @@ HTML
                        try {
                                $this->showDiff();
                        } catch ( MWContentSerializationException $ex ) {
-                               $msg = wfMessage(
+                               $msg = $this->context->msg(
                                        'content-failed-to-parse',
                                        $this->contentModel,
                                        $this->contentFormat,
@@ -3408,8 +3409,8 @@ HTML
                }
 
                if ( ( $oldContent && !$oldContent->isEmpty() ) || ( $newContent && !$newContent->isEmpty() ) ) {
-                       $oldtitle = wfMessage( $oldtitlemsg )->parse();
-                       $newtitle = wfMessage( 'yourtext' )->parse();
+                       $oldtitle = $this->context->msg( $oldtitlemsg )->parse();
+                       $newtitle = $this->context->msg( 'yourtext' )->parse();
 
                        if ( !$oldContent ) {
                                $oldContent = $newContent->getContentHandler()->makeEmptyContent();
@@ -3436,7 +3437,7 @@ HTML
         */
        protected function showHeaderCopyrightWarning() {
                $msg = 'editpage-head-copy-warn';
-               if ( !wfMessage( $msg )->isDisabled() ) {
+               if ( !$this->context->msg( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->wrapWikiMsg( "<div class='editpage-head-copywarn'>\n$1\n</div>",
                                'editpage-head-copy-warn' );
@@ -3454,7 +3455,7 @@ HTML
        protected function showTosSummary() {
                $msg = 'editpage-tos-summary';
                Hooks::run( 'EditPageTosSummary', [ $this->mTitle, &$msg ] );
-               if ( !wfMessage( $msg )->isDisabled() ) {
+               if ( !$this->context->msg( $msg )->isDisabled() ) {
                        global $wgOut;
                        $wgOut->addHTML( '<div class="mw-tos-summary">' );
                        $wgOut->addWikiMsg( $msg );
@@ -3465,7 +3466,7 @@ HTML
        protected function showEditTools() {
                global $wgOut;
                $wgOut->addHTML( '<div class="mw-editTools">' .
-                       wfMessage( 'edittools' )->inContentLanguage()->parse() .
+                       $this->context->msg( 'edittools' )->inContentLanguage()->parse() .
                        '</div>' );
        }
 
@@ -3500,7 +3501,7 @@ HTML
                Hooks::run( 'EditPageCopyrightWarning', [ $title, &$copywarnMsg ] );
 
                return "<div id=\"editpage-copywarn\">\n" .
-                       call_user_func_array( 'wfMessage', $copywarnMsg )->$format() . "\n</div>";
+                       call_user_func_array( 'wfMessage', $copywarnMsg )->title( $title )->$format() . "\n</div>";
        }
 
        /**
@@ -3547,19 +3548,19 @@ HTML
                if ( $cancel !== '' ) {
                        $cancel .= Html::element( 'span',
                                [ 'class' => 'mw-editButtons-pipe-separator' ],
-                               wfMessage( 'pipe-separator' )->text() );
+                               $this->context->msg( 'pipe-separator' )->text() );
                }
 
-               $message = wfMessage( 'edithelppage' )->inContentLanguage()->text();
+               $message = $this->context->msg( 'edithelppage' )->inContentLanguage()->text();
                $edithelpurl = Skin::makeInternalOrExternalUrl( $message );
                $attrs = [
                        'target' => 'helpwindow',
                        'href' => $edithelpurl,
                ];
-               $edithelp = Html::linkButton( wfMessage( 'edithelp' )->text(),
+               $edithelp = Html::linkButton( $this->context->msg( 'edithelp' )->text(),
                        $attrs, [ 'mw-ui-quiet' ] ) .
-                       wfMessage( 'word-separator' )->escaped() .
-                       wfMessage( 'newwindow' )->parse();
+                       $this->context->msg( 'word-separator' )->escaped() .
+                       $this->context->msg( 'newwindow' )->parse();
 
                $wgOut->addHTML( "      <span class='cancelLink'>{$cancel}</span>\n" );
                $wgOut->addHTML( "      <span class='editHelp'>{$edithelp}</span>\n" );
@@ -3597,8 +3598,8 @@ HTML
                        $de = $handler->createDifferenceEngine( $this->mArticle->getContext() );
                        $de->setContent( $content2, $content1 );
                        $de->showDiff(
-                               wfMessage( 'yourtext' )->parse(),
-                               wfMessage( 'storedversion' )->text()
+                               $this->context->msg( 'yourtext' )->parse(),
+                               $this->context->msg( 'storedversion' )->text()
                        );
 
                        $wgOut->wrapWikiMsg( '<h2>$1</h2>', "yourtext" );
@@ -3620,7 +3621,7 @@ HTML
 
                return Linker::linkKnown(
                        $this->getContextTitle(),
-                       wfMessage( 'cancel' )->parse(),
+                       $this->context->msg( 'cancel' )->parse(),
                        Html::buttonAttributes( $attrs, [ 'mw-ui-quiet' ] ),
                        $cancelParams
                );
@@ -3697,11 +3698,11 @@ HTML
                // Quick paranoid permission checks...
                if ( is_object( $data ) ) {
                        if ( $data->log_deleted & LogPage::DELETED_USER ) {
-                               $data->user_name = wfMessage( 'rev-deleted-user' )->escaped();
+                               $data->user_name = $this->context->msg( 'rev-deleted-user' )->escaped();
                        }
 
                        if ( $data->log_deleted & LogPage::DELETED_COMMENT ) {
-                               $data->log_comment = wfMessage( 'rev-deleted-comment' )->escaped();
+                               $data->log_comment = $this->context->msg( 'rev-deleted-comment' )->escaped();
                        }
                }
 
@@ -3728,7 +3729,8 @@ HTML
                                // string, which happens when you initially edit
                                // a category page, due to automatic preview-on-open.
                                $parsedNote = $wgOut->parse( "<div class='previewnote'>" .
-                                       wfMessage( 'session_fail_preview_html' )->text() . "</div>", true, /* interface */true );
+                                       $this->context->msg( 'session_fail_preview_html' )->text() . "</div>",
+                                       true, /* interface */true );
                        }
                        $stats->increment( 'edit.failures.session_loss' );
                        return $parsedNote;
@@ -3750,22 +3752,22 @@ HTML
                        # provide a anchor link to the editform
                        $continueEditing = '<span class="mw-continue-editing">' .
                                '[[#' . self::EDITFORM_ID . '|' . $wgLang->getArrow() . ' ' .
-                               wfMessage( 'continue-editing' )->text() . ']]</span>';
+                               $this->context->msg( 'continue-editing' )->text() . ']]</span>';
                        if ( $this->mTriedSave && !$this->mTokenOk ) {
                                if ( $this->mTokenOkExceptSuffix ) {
-                                       $note = wfMessage( 'token_suffix_mismatch' )->plain();
+                                       $note = $this->context->msg( 'token_suffix_mismatch' )->plain();
                                        $stats->increment( 'edit.failures.bad_token' );
                                } else {
-                                       $note = wfMessage( 'session_fail_preview' )->plain();
+                                       $note = $this->context->msg( 'session_fail_preview' )->plain();
                                        $stats->increment( 'edit.failures.session_loss' );
                                }
                        } elseif ( $this->incompleteForm ) {
-                               $note = wfMessage( 'edit_form_incomplete' )->plain();
+                               $note = $this->context->msg( 'edit_form_incomplete' )->plain();
                                if ( $this->mTriedSave ) {
                                        $stats->increment( 'edit.failures.incomplete_form' );
                                }
                        } else {
-                               $note = wfMessage( 'previewnote' )->plain() . ' ' . $continueEditing;
+                               $note = $this->context->msg( 'previewnote' )->plain() . ' ' . $continueEditing;
                        }
 
                        # don't parse non-wikitext pages, show message about preview
@@ -3796,7 +3798,7 @@ HTML
                                # Messages: usercsspreview, userjspreview, sitecsspreview, sitejspreview
                                if ( $level && $format ) {
                                        $note = "<div id='mw-{$level}{$format}preview'>" .
-                                               wfMessage( "{$level}{$format}preview" )->text() .
+                                               $this->context->msg( "{$level}{$format}preview" )->text() .
                                                ' ' . $continueEditing . "</div>";
                                }
                        }
@@ -3822,7 +3824,7 @@ HTML
                        }
 
                } catch ( MWContentSerializationException $ex ) {
-                       $m = wfMessage(
+                       $m = $this->context->msg(
                                'content-failed-to-parse',
                                $this->contentModel,
                                $this->contentFormat,
@@ -3834,13 +3836,13 @@ HTML
 
                if ( $this->isConflict ) {
                        $conflict = '<h2 id="mw-previewconflict">'
-                               . wfMessage( 'previewconflict' )->escaped() . "</h2>\n";
+                               . $this->context->msg( 'previewconflict' )->escaped() . "</h2>\n";
                } else {
                        $conflict = '<hr />';
                }
 
                $previewhead = "<div class='previewnote'>\n" .
-                       '<h2 id="mw-previewheader">' . wfMessage( 'preview' )->escaped() . "</h2>" .
+                       '<h2 id="mw-previewheader">' . $this->context->msg( 'preview' )->escaped() . "</h2>" .
                        $wgOut->parse( $note, true, /* interface */true ) . $conflict . "</div>\n";
 
                $pageViewLang = $this->mTitle->getPageViewLanguage();
@@ -4060,11 +4062,11 @@ HTML
                // don't show the minor edit checkbox if it's a new page or section
                if ( !$this->isNew ) {
                        $checkboxes['minor'] = '';
-                       $minorLabel = wfMessage( 'minoredit' )->parse();
+                       $minorLabel = $this->context->msg( 'minoredit' )->parse();
                        if ( $wgUser->isAllowed( 'minoredit' ) ) {
                                $attribs = [
                                        'tabindex' => ++$tabindex,
-                                       'accesskey' => wfMessage( 'accesskey-minoredit' )->text(),
+                                       'accesskey' => $this->context->msg( 'accesskey-minoredit' )->text(),
                                        'id' => 'wpMinoredit',
                                ];
                                $minorEditHtml =
@@ -4083,12 +4085,12 @@ HTML
                        }
                }
 
-               $watchLabel = wfMessage( 'watchthis' )->parse();
+               $watchLabel = $this->context->msg( 'watchthis' )->parse();
                $checkboxes['watch'] = '';
                if ( $wgUser->isLoggedIn() ) {
                        $attribs = [
                                'tabindex' => ++$tabindex,
-                               'accesskey' => wfMessage( 'accesskey-watch' )->text(),
+                               'accesskey' => $this->context->msg( 'accesskey-watch' )->text(),
                                'id' => 'wpWatchthis',
                        ];
                        $watchThisHtml =
@@ -4128,7 +4130,7 @@ HTML
                } else {
                        $buttonLabelKey = !$this->mTitle->exists() ? 'savearticle' : 'savechanges';
                }
-               $buttonLabel = wfMessage( $buttonLabelKey )->text();
+               $buttonLabel = $this->context->msg( $buttonLabelKey )->text();
                $attribs = [
                        'id' => 'wpSave',
                        'name' => 'wpSave',
@@ -4142,7 +4144,7 @@ HTML
                        'name' => 'wpPreview',
                        'tabindex' => $tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'preview' );
-               $buttons['preview'] = Html::submitButton( wfMessage( 'showpreview' )->text(),
+               $buttons['preview'] = Html::submitButton( $this->context->msg( 'showpreview' )->text(),
                        $attribs );
                $buttons['live'] = '';
 
@@ -4151,7 +4153,7 @@ HTML
                        'name' => 'wpDiff',
                        'tabindex' => ++$tabindex,
                ] + Linker::tooltipAndAccesskeyAttribs( 'diff' );
-               $buttons['diff'] = Html::submitButton( wfMessage( 'showdiff' )->text(),
+               $buttons['diff'] = Html::submitButton( $this->context->msg( 'showdiff' )->text(),
                        $attribs );
 
                Hooks::run( 'EditPageBeforeEditButtons', [ &$this, &$buttons, &$tabindex ] );
@@ -4165,9 +4167,9 @@ HTML
        function noSuchSectionPage() {
                global $wgOut;
 
-               $wgOut->prepareErrorPage( wfMessage( 'nosuchsectiontitle' ) );
+               $wgOut->prepareErrorPage( $this->context->msg( 'nosuchsectiontitle' ) );
 
-               $res = wfMessage( 'nosuchsectiontext', $this->section )->parseAsBlock();
+               $res = $this->context->msg( 'nosuchsectiontext', $this->section )->parseAsBlock();
                Hooks::run( 'EditPageNoSuchSection', [ &$this, &$res ] );
                $wgOut->addHTML( $res );
 
@@ -4186,7 +4188,7 @@ HTML
                if ( is_array( $match ) ) {
                        $match = $wgLang->listToText( $match );
                }
-               $wgOut->prepareErrorPage( wfMessage( 'spamprotectiontitle' ) );
+               $wgOut->prepareErrorPage( $this->context->msg( 'spamprotectiontitle' ) );
 
                $wgOut->addHTML( '<div id="spamprotected">' );
                $wgOut->addWikiMsg( 'spamprotectiontext' );
index 9bbbd35..2aa4b80 100644 (file)
@@ -517,6 +517,7 @@ class MediaWiki {
         */
        public function run() {
                try {
+                       $this->setDBProfilingAgent();
                        try {
                                $this->main();
                        } catch ( ErrorPageError $e ) {
@@ -533,6 +534,15 @@ class MediaWiki {
                $this->doPostOutputShutdown( 'normal' );
        }
 
+       private function setDBProfilingAgent() {
+               $services = MediaWikiServices::getInstance();
+               // Add a comment for easy SHOW PROCESSLIST interpretation
+               $name = $this->context->getUser()->getName();
+               $services->getDBLoadBalancerFactory()->setAgentName(
+                       mb_strlen( $name ) > 15 ? mb_substr( $name, 0, 15 ) . '...' : $name
+               );
+       }
+
        /**
         * @see MediaWiki::preOutputCommit()
         * @param callable $postCommitWork [default: null]
index a8b8ad1..6d9ddd6 100644 (file)
@@ -3258,7 +3258,7 @@ class Title implements LinkTarget {
         * This clears some fields in this object, and clears any associated
         * keys in the "bad links" section of the link cache.
         *
-        * - This is called from WikiPage::doEdit() and WikiPage::insertOn() to allow
+        * - This is called from WikiPage::doEditContent() and WikiPage::insertOn() to allow
         * loading of the new page_id. It's also called from
         * WikiPage::doDeleteArticleReal()
         *
index ee9150c..d6de834 100644 (file)
@@ -532,7 +532,7 @@ class ApiEditPage extends ApiBase {
 
                        case EditPage::AS_END:
                        default:
-                               // $status came from WikiPage::doEdit()
+                               // $status came from WikiPage::doEditContent()
                                $errors = $status->getErrorsArray();
                                $this->dieUsageMsg( $errors[0] ); // TODO: Add new errors to message map
                                break;
index a82f018..a485531 100644 (file)
@@ -51,15 +51,18 @@ class EmailNotificationSecondaryAuthenticationProvider
                        && !$this->manager->getAuthenticationSessionData( 'no-email' )
                ) {
                        // TODO show 'confirmemail_oncreate'/'confirmemail_sendfailed' message
-                       wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $user ) {
-                               $user = $user->getInstanceForUpdate();
-                               $status = $user->sendConfirmationMail();
-                               $user->saveSettings();
-                               if ( !$status->isGood() ) {
-                                       $this->logger->warning( 'Could not send confirmation email: ' .
-                                               $status->getWikiText( false, false, 'en' ) );
-                               }
-                       } );
+                       wfGetDB( DB_MASTER )->onTransactionIdle(
+                               function () use ( $user ) {
+                                       $user = $user->getInstanceForUpdate();
+                                       $status = $user->sendConfirmationMail();
+                                       $user->saveSettings();
+                                       if ( !$status->isGood() ) {
+                                               $this->logger->warning( 'Could not send confirmation email: ' .
+                                                       $status->getWikiText( false, false, 'en' ) );
+                                       }
+                               },
+                               __METHOD__
+                       );
                }
 
                return AuthenticationResponse::newPass();
index f5571c7..f16423d 100644 (file)
@@ -304,10 +304,13 @@ class TemporaryPasswordPrimaryAuthenticationProvider
 
                if ( $sendMail ) {
                        // Send email after DB commit
-                       $dbw->onTransactionIdle( function () use ( $req ) {
-                               /** @var TemporaryPasswordAuthenticationRequest $req */
-                               $this->sendPasswordResetEmail( $req );
-                       } );
+                       $dbw->onTransactionIdle(
+                               function () use ( $req ) {
+                                       /** @var TemporaryPasswordAuthenticationRequest $req */
+                                       $this->sendPasswordResetEmail( $req );
+                               },
+                               __METHOD__
+                       );
                }
        }
 
@@ -375,9 +378,12 @@ class TemporaryPasswordPrimaryAuthenticationProvider
 
                if ( $mailpassword ) {
                        // Send email after DB commit
-                       wfGetDB( DB_MASTER )->onTransactionIdle( function () use ( $user, $creator, $req ) {
-                               $this->sendNewAccountEmail( $user, $creator, $req->password );
-                       } );
+                       wfGetDB( DB_MASTER )->onTransactionIdle(
+                               function () use ( $user, $creator, $req ) {
+                                       $this->sendNewAccountEmail( $user, $creator, $req->password );
+                               },
+                               __METHOD__
+                       );
                }
 
                return $mailpassword ? 'byemail' : null;
index d254d3d..e871855 100644 (file)
@@ -1089,7 +1089,7 @@ class MessageCache {
                if ( !$title || !$title instanceof Title ) {
                        global $wgTitle;
                        wfDebugLog( 'GlobalTitleFail', __METHOD__ . ' called by ' .
-                               wfGetAllCallers( 5 ) . ' with no title set.' );
+                               wfGetAllCallers( 6 ) . ' with no title set.' );
                        $title = $wgTitle;
                }
                // Sometimes $wgTitle isn't set either...
index 8e74674..590fd37 100644 (file)
@@ -342,18 +342,21 @@ class RecentChange {
                        ) {
                                // @FIXME: This would be better as an extension hook
                                // Send emails or email jobs once this row is safely committed
-                               $dbw->onTransactionIdle( function () use ( $editor, $title ) {
-                                       $enotif = new EmailNotification();
-                                       $enotif->notifyOnPageChange(
-                                               $editor,
-                                               $title,
-                                               $this->mAttribs['rc_timestamp'],
-                                               $this->mAttribs['rc_comment'],
-                                               $this->mAttribs['rc_minor'],
-                                               $this->mAttribs['rc_last_oldid'],
-                                               $this->mExtra['pageStatus']
-                                       );
-                               } );
+                               $dbw->onTransactionIdle(
+                                       function () use ( $editor, $title ) {
+                                               $enotif = new EmailNotification();
+                                               $enotif->notifyOnPageChange(
+                                                       $editor,
+                                                       $title,
+                                                       $this->mAttribs['rc_timestamp'],
+                                                       $this->mAttribs['rc_comment'],
+                                                       $this->mAttribs['rc_minor'],
+                                                       $this->mAttribs['rc_last_oldid'],
+                                                       $this->mExtra['pageStatus']
+                                               );
+                                       },
+                                       __METHOD__
+                               );
                        }
                }
 
index 3fa1335..6c1e37f 100644 (file)
@@ -62,8 +62,10 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mDBname;
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
        protected $tableAliases = [];
-       /** @var bool */
+       /** @var bool Whether this PHP instance is for a CLI script */
        protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
 
        /** @var BagOStuff APC cache */
        protected $srvCache;
@@ -85,7 +87,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        protected $mTrxPreCommitCallbacks = [];
        /** @var array[] List of (callable, method name) */
        protected $mTrxEndCallbacks = [];
-       /** @var array[] Map of (name => (callable, method name)) */
+       /** @var callable[] Map of (name => callable) */
        protected $mTrxRecurringCallbacks = [];
        /** @var bool Whether to suppress triggering of transaction end callbacks */
        protected $mTrxEndCallbacksSuppressed = false;
@@ -251,6 +253,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                $this->cliMode = isset( $params['cliMode'] )
                        ? $params['cliMode']
                        : ( PHP_SAPI === 'cli' );
+               $this->agent = isset( $params['agent'] )
+                       ? str_replace( '/', '-', $params['agent'] ) // escape for comment
+                       : '';
 
                $this->mFlags = $flags;
                if ( $this->mFlags & DBO_DEFAULT ) {
@@ -308,8 +313,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         * @throws InvalidArgumentException If the database driver or extension cannot be found
         */
        final public static function factory( $dbType, $p = [] ) {
-               global $wgCommandLineMode;
-
                $canonicalDBTypes = [
                        'mysql' => [ 'mysqli', 'mysql' ],
                        'postgres' => [],
@@ -367,7 +370,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                                $p['schema'] = isset( $defaultSchemas[$dbType] ) ? $defaultSchemas[$dbType] : null;
                        }
                        $p['foreign'] = isset( $p['foreign'] ) ? $p['foreign'] : false;
-                       $p['cliMode'] = $wgCommandLineMode;
 
                        $conn = new $class( $p );
                        if ( isset( $p['connLogger'] ) ) {
@@ -379,7 +381,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                        if ( isset( $p['errorLogger'] ) ) {
                                $conn->errorLogger = $p['errorLogger'];
                        } else {
-                               $conn->errorLogger = [ MWExceptionHandler::class, 'logException' ];
+                               $conn->errorLogger = function ( Exception $e ) {
+                                       trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
+                               };
                        }
                } else {
                        $conn = null;
@@ -410,18 +414,25 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         *   - false to disable debugging
         *   - omitted or null to do nothing
         *
-        * @return bool|null Previous value of the flag
+        * @return bool Previous value of the flag
+        * @deprecated since 1.28; use setFlag()
         */
        public function debug( $debug = null ) {
-               return wfSetBit( $this->mFlags, DBO_DEBUG, $debug );
+               $res = $this->getFlag( DBO_DEBUG );
+               if ( $debug !== null ) {
+                       $debug ? $this->setFlag( DBO_DEBUG ) : $this->clearFlag( DBO_DEBUG );
+               }
+
+               return $res;
        }
 
        public function bufferResults( $buffer = null ) {
-               if ( is_null( $buffer ) ) {
-                       return !(bool)( $this->mFlags & DBO_NOBUFFER );
-               } else {
-                       return !wfSetBit( $this->mFlags, DBO_NOBUFFER, !$buffer );
+               $res = !$this->getFlag( DBO_NOBUFFER );
+               if ( $buffer !== null ) {
+                       $buffer ? $this->clearFlag( DBO_NOBUFFER ) : $this->setFlag( DBO_NOBUFFER );
                }
+
+               return $res;
        }
 
        /**
@@ -437,7 +448,12 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
         * @return bool The previous value of the flag.
         */
        protected function ignoreErrors( $ignoreErrors = null ) {
-               return wfSetBit( $this->mFlags, DBO_IGNORE, $ignoreErrors );
+               $res = $this->getFlag( DBO_IGNORE );
+               if ( $ignoreErrors !== null ) {
+                       $ignoreErrors ? $this->setFlag( DBO_IGNORE ) : $this->clearFlag( DBO_IGNORE );
+               }
+
+               return $res;
        }
 
        public function trxLevel() {
@@ -449,11 +465,17 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        }
 
        public function tablePrefix( $prefix = null ) {
-               return wfSetVar( $this->mTablePrefix, $prefix );
+               $old = $this->mTablePrefix;
+               $this->mTablePrefix = $prefix;
+
+               return $old;
        }
 
        public function dbSchema( $schema = null ) {
-               return wfSetVar( $this->mSchema, $schema );
+               $old = $this->mSchema;
+               $this->mSchema = $schema;
+
+               return $old;
        }
 
        /**
@@ -831,8 +853,6 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
        }
 
        public function query( $sql, $fname = __METHOD__, $tempIgnore = false ) {
-               global $wgUser;
-
                $priorWritesPending = $this->writesOrCallbacksPending();
                $this->mLastQuery = $sql;
 
@@ -846,20 +866,9 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                        $this->mDoneWrites = microtime( true );
                }
 
-               # Add a comment for easy SHOW PROCESSLIST interpretation
-               if ( is_object( $wgUser ) && $wgUser->isItemLoaded( 'name' ) ) {
-                       $userName = $wgUser->getName();
-                       if ( mb_strlen( $userName ) > 15 ) {
-                               $userName = mb_substr( $userName, 0, 15 ) . '...';
-                       }
-                       $userName = str_replace( '/', '', $userName );
-               } else {
-                       $userName = '';
-               }
-
                // Add trace comment to the begin of the sql string, right after the operator.
                // Or, for one-word queries (like "BEGIN" or COMMIT") add it to the end (bug 42598)
-               $commentedSql = preg_replace( '/\s|$/', " /* $fname $userName */ ", $sql, 1 );
+               $commentedSql = preg_replace( '/\s|$/', " /* $fname {$this->agent} */ ", $sql, 1 );
 
                # Start implicit transactions that wrap the request if DBO_TRX is enabled
                if ( !$this->mTrxLevel && $this->getFlag( DBO_TRX )
@@ -2671,23 +2680,23 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                return false;
        }
 
-       final public function onTransactionResolution( callable $callback ) {
+       final public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
                if ( !$this->mTrxLevel ) {
                        throw new DBUnexpectedError( $this, "No transaction is active." );
                }
-               $this->mTrxEndCallbacks[] = [ $callback, wfGetCaller() ];
+               $this->mTrxEndCallbacks[] = [ $callback, $fname ];
        }
 
-       final public function onTransactionIdle( callable $callback ) {
-               $this->mTrxIdleCallbacks[] = [ $callback, wfGetCaller() ];
+       final public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
+               $this->mTrxIdleCallbacks[] = [ $callback, $fname ];
                if ( !$this->mTrxLevel ) {
                        $this->runOnTransactionIdleCallbacks( self::TRIGGER_IDLE );
                }
        }
 
-       final public function onTransactionPreCommitOrIdle( callable $callback ) {
+       final public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
                if ( $this->mTrxLevel ) {
-                       $this->mTrxPreCommitCallbacks[] = [ $callback, wfGetCaller() ];
+                       $this->mTrxPreCommitCallbacks[] = [ $callback, $fname ];
                } else {
                        // If no transaction is active, then make one for this callback
                        $this->startAtomic( __METHOD__ );
@@ -2703,7 +2712,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
 
        final public function setTransactionListener( $name, callable $callback = null ) {
                if ( $callback ) {
-                       $this->mTrxRecurringCallbacks[$name] = [ $callback, wfGetCaller() ];
+                       $this->mTrxRecurringCallbacks[$name] = $callback;
                } else {
                        unset( $this->mTrxRecurringCallbacks[$name] );
                }
@@ -2818,9 +2827,8 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                /** @var Exception $e */
                $e = null; // first exception
 
-               foreach ( $this->mTrxRecurringCallbacks as $callback ) {
+               foreach ( $this->mTrxRecurringCallbacks as $phpCallback ) {
                        try {
-                               list( $phpCallback ) = $callback;
                                $phpCallback( $trigger, $this );
                        } catch ( Exception $ex ) {
                                call_user_func( $this->errorLogger, $ex );
@@ -2907,7 +2915,7 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                $this->mTrxAutomatic = ( $mode === self::TRANSACTION_INTERNAL );
                $this->mTrxAutomaticAtomic = false;
                $this->mTrxAtomicLevels = [];
-               $this->mTrxShortId = wfRandomString( 12 );
+               $this->mTrxShortId = sprintf( '%06x', mt_rand( 0, 0xffffff ) );
                $this->mTrxWriteDuration = 0.0;
                $this->mTrxWriteQueryCount = 0;
                $this->mTrxWriteAdjDuration = 0.0;
@@ -3527,15 +3535,18 @@ abstract class DatabaseBase implements IDatabase, LoggerAwareInterface {
                                // There is a good chance an exception was thrown, causing any early return
                                // from the caller. Let any error handler get a chance to issue rollback().
                                // If there isn't one, let the error bubble up and trigger server-side rollback.
-                               $this->onTransactionResolution( function () use ( $lockKey, $fname ) {
-                                       $this->unlock( $lockKey, $fname );
-                               } );
+                               $this->onTransactionResolution(
+                                       function () use ( $lockKey, $fname ) {
+                                               $this->unlock( $lockKey, $fname );
+                                       },
+                                       $fname
+                               );
                        } else {
                                $this->unlock( $lockKey, $fname );
                        }
                } );
 
-               $this->commit( __METHOD__, self::FLUSHING_INTERNAL );
+               $this->commit( $fname, self::FLUSHING_INTERNAL );
 
                return $unlocker;
        }
index 59c8952..4ffafde 100644 (file)
@@ -165,7 +165,7 @@ class DatabaseMssql extends Database {
         * @throws DBUnexpectedError
         */
        protected function doQuery( $sql ) {
-               if ( $this->debug() ) {
+               if ( $this->getFlag( DBO_DEBUG ) ) {
                        wfDebug( "SQL: [$sql]\n" );
                }
                $this->offset = 0;
index 87fd81b..33c48a5 100644 (file)
@@ -37,6 +37,8 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
         * @TODO: inject objects via dependency framework
         */
        public function __construct( array $conf ) {
+               global $wgCommandLineMode;
+
                $defaults = [
                        'domain' => wfWikiID(),
                        'hostname' => wfHostname(),
@@ -61,6 +63,9 @@ abstract class LBFactoryMW extends LBFactory implements DestructibleService {
                        $defaults['wanCache'] = $wCache;
                }
 
+               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
+               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : $wgCommandLineMode;
+
                parent::__construct( $conf + $defaults );
        }
 
index a348719..6585575 100644 (file)
@@ -24,7 +24,7 @@ class AtomicSectionUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->callback = $callback;
 
                if ( $this->dbw->trxLevel() ) {
-                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index d26cf9d..d61dec2 100644 (file)
@@ -23,7 +23,7 @@ class AutoCommitUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->callback = $callback;
 
                if ( $this->dbw->trxLevel() ) {
-                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $this->dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index e24a9c0..d18349b 100644 (file)
@@ -174,9 +174,12 @@ class LinksUpdate extends DataUpdate implements EnqueueableDataUpdate {
                // Commit and release the lock (if set)
                ScopedCallback::consume( $scopedLock );
                // Run post-commit hooks without DBO_TRX
-               $this->getDB()->onTransactionIdle( function() {
-                       Hooks::run( 'LinksUpdateComplete', [ &$this ] );
-               } );
+               $this->getDB()->onTransactionIdle(
+                       function () {
+                               Hooks::run( 'LinksUpdateComplete', [ &$this ] );
+                       },
+                       __METHOD__
+               );
        }
 
        /**
index 47b162c..5247e97 100644 (file)
@@ -19,7 +19,7 @@ class MWCallableUpdate implements DeferrableUpdate, DeferrableCallback {
                $this->fname = $fname;
 
                if ( $dbw && $dbw->trxLevel() ) {
-                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ] );
+                       $dbw->onTransactionResolution( [ $this, 'cancelOnRollback' ], $fname );
                }
        }
 
index fccb755..7b40a7b 100644 (file)
@@ -500,9 +500,12 @@ class LocalRepo extends FileRepo {
        function invalidateImageRedirect( Title $title ) {
                $key = $this->getSharedCacheKey( 'image_redirect', md5( $title->getDBkey() ) );
                if ( $key ) {
-                       $this->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
-                               ObjectCache::getMainWANInstance()->delete( $key );
-                       } );
+                       $this->getMasterDB()->onTransactionPreCommitOrIdle(
+                               function () use ( $key ) {
+                                       ObjectCache::getMainWANInstance()->delete( $key );
+                               },
+                               __METHOD__
+                       );
                }
        }
 
index d63a91b..618272c 100644 (file)
@@ -313,9 +313,12 @@ class LocalFile extends File {
                        return;
                }
 
-               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle( function() use ( $key ) {
-                       ObjectCache::getMainWANInstance()->delete( $key );
-               } );
+               $this->repo->getMasterDB()->onTransactionPreCommitOrIdle(
+                       function () use ( $key ) {
+                               ObjectCache::getMainWANInstance()->delete( $key );
+                       },
+                       __METHOD__
+               );
        }
 
        /**
@@ -2002,12 +2005,15 @@ class LocalFile extends File {
                        }
                        // Release the lock *after* commit to avoid row-level contention.
                        // Make sure it triggers on rollback() as well as commit() (T132921).
-                       $dbw->onTransactionResolution( function () use ( $logger ) {
-                               $status = $this->releaseFileLock();
-                               if ( !$status->isGood() ) {
-                                       $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
-                               }
-                       } );
+                       $dbw->onTransactionResolution(
+                               function () use ( $logger ) {
+                                       $status = $this->releaseFileLock();
+                                       if ( !$status->isGood() ) {
+                                               $logger->error( "Failed to unlock '{file}'", [ 'file' => $this->name ] );
+                                       }
+                               },
+                               __METHOD__
+                       );
                        // Callers might care if the SELECT snapshot is safely fresh
                        $this->lockedOwnTrx = $makesTransaction;
                }
index 50727a2..856cdfd 100644 (file)
@@ -186,7 +186,8 @@ class JobQueueDB extends JobQueue {
                $dbw->onTransactionIdle(
                        function () use ( $dbw, $jobs, $flags, $method ) {
                                $this->doBatchPushInternal( $dbw, $jobs, $flags, $method );
-                       }
+                       },
+                       __METHOD__
                );
        }
 
@@ -494,15 +495,18 @@ class JobQueueDB extends JobQueue {
                // jobs to become no-ops without any actual jobs that made them redundant.
                $dbw = $this->getMasterDB();
                $cache = $this->dupCache;
-               $dbw->onTransactionIdle( function () use ( $cache, $params, $key, $dbw ) {
-                       $timestamp = $cache->get( $key ); // current last timestamp of this job
-                       if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
-                               return true; // a newer version of this root job was enqueued
-                       }
+               $dbw->onTransactionIdle(
+                       function () use ( $cache, $params, $key, $dbw ) {
+                               $timestamp = $cache->get( $key ); // current last timestamp of this job
+                               if ( $timestamp && $timestamp >= $params['rootJobTimestamp'] ) {
+                                       return true; // a newer version of this root job was enqueued
+                               }
 
-                       // Update the timestamp of the last root job started at the location...
-                       return $cache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
-               } );
+                               // Update the timestamp of the last root job started at the location...
+                               return $cache->set( $key, $params['rootJobTimestamp'], JobQueueDB::ROOTJOB_TTL );
+                       },
+                       __METHOD__
+               );
 
                return true;
        }
index 809fb63..0e90674 100644 (file)
@@ -19,6 +19,7 @@
  * @author Aaron Schulz
  * @ingroup JobQueue
  */
+use MediaWiki\MediaWikiServices;
 
 /**
  * Job for pruning recent changes
@@ -81,7 +82,7 @@ class RecentChangesUpdateJob extends Job {
                        return; // already in progress
                }
 
-               $factory = wfGetLBFactory();
+               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
                $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
                $cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
                do {
@@ -119,109 +120,112 @@ class RecentChangesUpdateJob extends Job {
                $dbw = wfGetDB( DB_MASTER );
                // JobRunner uses DBO_TRX, but doesn't call begin/commit itself;
                // onTransactionIdle() will run immediately since there is no trx.
-               $dbw->onTransactionIdle( function() use ( $dbw, $days, $window ) {
-                       $factory = wfGetLBFactory();
-                       $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
-                       // Avoid disconnect/ping() cycle that makes locks fall off
-                       $dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
-
-                       $lockKey = wfWikiID() . '-activeusers';
-                       if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
-                               return; // exclusive update (avoids duplicate entries)
-                       }
-
-                       $nowUnix = time();
-                       // Get the last-updated timestamp for the cache
-                       $cTime = $dbw->selectField( 'querycache_info',
-                               'qci_timestamp',
-                               [ 'qci_type' => 'activeusers' ]
-                       );
-                       $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
-
-                       // Pick the date range to fetch from. This is normally from the last
-                       // update to till the present time, but has a limited window for sanity.
-                       // If the window is limited, multiple runs are need to fully populate it.
-                       $sTimestamp = max( $cTimeUnix, $nowUnix - $days * 86400 );
-                       $eTimestamp = min( $sTimestamp + $window, $nowUnix );
-
-                       // Get all the users active since the last update
-                       $res = $dbw->select(
-                               [ 'recentchanges' ],
-                               [ 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ],
-                               [
-                                       'rc_user > 0', // actual accounts
-                                       'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
-                                       'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
-                                       'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
-                                       'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
-                               ],
-                               __METHOD__,
-                               [
-                                       'GROUP BY' => [ 'rc_user_text' ],
-                                       'ORDER BY' => 'NULL' // avoid filesort
-                               ]
-                       );
-                       $names = [];
-                       foreach ( $res as $row ) {
-                               $names[$row->rc_user_text] = $row->lastedittime;
-                       }
-
-                       // Rotate out users that have not edited in too long (according to old data set)
-                       $dbw->delete( 'querycachetwo',
-                               [
-                                       'qcc_type' => 'activeusers',
-                                       'qcc_value < ' . $dbw->addQuotes( $nowUnix - $days * 86400 ) // TS_UNIX
-                               ],
-                               __METHOD__
-                       );
+               $dbw->onTransactionIdle(
+                       function () use ( $dbw, $days, $window ) {
+                               $factory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+                               $ticket = $factory->getEmptyTransactionTicket( __METHOD__ );
+                               // Avoid disconnect/ping() cycle that makes locks fall off
+                               $dbw->setSessionOptions( [ 'connTimeout' => 900 ] );
+
+                               $lockKey = wfWikiID() . '-activeusers';
+                               if ( !$dbw->lock( $lockKey, __METHOD__, 1 ) ) {
+                                       return; // exclusive update (avoids duplicate entries)
+                               }
 
-                       // Find which of the recently active users are already accounted for
-                       if ( count( $names ) ) {
-                               $res = $dbw->select( 'querycachetwo',
-                                       [ 'user_name' => 'qcc_title' ],
+                               $nowUnix = time();
+                               // Get the last-updated timestamp for the cache
+                               $cTime = $dbw->selectField( 'querycache_info',
+                                       'qci_timestamp',
+                                       [ 'qci_type' => 'activeusers' ]
+                               );
+                               $cTimeUnix = $cTime ? wfTimestamp( TS_UNIX, $cTime ) : 1;
+
+                               // Pick the date range to fetch from. This is normally from the last
+                               // update to till the present time, but has a limited window for sanity.
+                               // If the window is limited, multiple runs are need to fully populate it.
+                               $sTimestamp = max( $cTimeUnix, $nowUnix - $days * 86400 );
+                               $eTimestamp = min( $sTimestamp + $window, $nowUnix );
+
+                               // Get all the users active since the last update
+                               $res = $dbw->select(
+                                       [ 'recentchanges' ],
+                                       [ 'rc_user_text', 'lastedittime' => 'MAX(rc_timestamp)' ],
                                        [
-                                               'qcc_type' => 'activeusers',
-                                               'qcc_namespace' => NS_USER,
-                                               'qcc_title' => array_keys( $names ) ],
-                                       __METHOD__
+                                               'rc_user > 0', // actual accounts
+                                               'rc_type != ' . $dbw->addQuotes( RC_EXTERNAL ), // no wikidata
+                                               'rc_log_type IS NULL OR rc_log_type != ' . $dbw->addQuotes( 'newusers' ),
+                                               'rc_timestamp >= ' . $dbw->addQuotes( $dbw->timestamp( $sTimestamp ) ),
+                                               'rc_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $eTimestamp ) )
+                                       ],
+                                       __METHOD__,
+                                       [
+                                               'GROUP BY' => [ 'rc_user_text' ],
+                                               'ORDER BY' => 'NULL' // avoid filesort
+                                       ]
                                );
+                               $names = [];
                                foreach ( $res as $row ) {
-                                       unset( $names[$row->user_name] );
+                                       $names[$row->rc_user_text] = $row->lastedittime;
                                }
-                       }
 
-                       // Insert the users that need to be added to the list
-                       if ( count( $names ) ) {
-                               $newRows = [];
-                               foreach ( $names as $name => $lastEditTime ) {
-                                       $newRows[] = [
+                               // Rotate out users that have not edited in too long (according to old data set)
+                               $dbw->delete( 'querycachetwo',
+                                       [
                                                'qcc_type' => 'activeusers',
-                                               'qcc_namespace' => NS_USER,
-                                               'qcc_title' => $name,
-                                               'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
-                                               'qcc_namespacetwo' => 0, // unused
-                                               'qcc_titletwo' => '' // unused
-                                       ];
+                                               'qcc_value < ' . $dbw->addQuotes( $nowUnix - $days * 86400 ) // TS_UNIX
+                                       ],
+                                       __METHOD__
+                               );
+
+                               // Find which of the recently active users are already accounted for
+                               if ( count( $names ) ) {
+                                       $res = $dbw->select( 'querycachetwo',
+                                               [ 'user_name' => 'qcc_title' ],
+                                               [
+                                                       'qcc_type' => 'activeusers',
+                                                       'qcc_namespace' => NS_USER,
+                                                       'qcc_title' => array_keys( $names ) ],
+                                               __METHOD__
+                                       );
+                                       foreach ( $res as $row ) {
+                                               unset( $names[$row->user_name] );
+                                       }
                                }
-                               foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
-                                       $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
-                                       $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+
+                               // Insert the users that need to be added to the list
+                               if ( count( $names ) ) {
+                                       $newRows = [];
+                                       foreach ( $names as $name => $lastEditTime ) {
+                                               $newRows[] = [
+                                                       'qcc_type' => 'activeusers',
+                                                       'qcc_namespace' => NS_USER,
+                                                       'qcc_title' => $name,
+                                                       'qcc_value' => wfTimestamp( TS_UNIX, $lastEditTime ),
+                                                       'qcc_namespacetwo' => 0, // unused
+                                                       'qcc_titletwo' => '' // unused
+                                               ];
+                                       }
+                                       foreach ( array_chunk( $newRows, 500 ) as $rowBatch ) {
+                                               $dbw->insert( 'querycachetwo', $rowBatch, __METHOD__ );
+                                               $factory->commitAndWaitForReplication( __METHOD__, $ticket );
+                                       }
                                }
-                       }
 
-                       // If a transaction was already started, it might have an old
-                       // snapshot, so kludge the timestamp range back as needed.
-                       $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() );
+                               // If a transaction was already started, it might have an old
+                               // snapshot, so kludge the timestamp range back as needed.
+                               $asOfTimestamp = min( $eTimestamp, (int)$dbw->trxTimestamp() );
 
-                       // Touch the data freshness timestamp
-                       $dbw->replace( 'querycache_info',
-                               [ 'qci_type' ],
-                               [ 'qci_type' => 'activeusers',
-                                       'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ], // not always $now
-                               __METHOD__
-                       );
+                               // Touch the data freshness timestamp
+                               $dbw->replace( 'querycache_info',
+                                       [ 'qci_type' ],
+                                       [ 'qci_type' => 'activeusers',
+                                               'qci_timestamp' => $dbw->timestamp( $asOfTimestamp ) ], // not always $now
+                                       __METHOD__
+                               );
 
-                       $dbw->unlock( $lockKey, __METHOD__ );
-               } );
+                               $dbw->unlock( $lockKey, __METHOD__ );
+                       },
+                       __METHOD__
+               );
        }
 }
index 5eafcb3..d76d866 100644 (file)
@@ -36,42 +36,45 @@ class PurgeJobUtils {
                        return;
                }
 
-               $dbw->onTransactionIdle( function() use ( $dbw, $namespace, $dbkeys ) {
-                       $services = MediaWikiServices::getInstance();
-                       $lbFactory = $services->getDBLoadBalancerFactory();
-                       // Determine which pages need to be updated.
-                       // This is necessary to prevent the job queue from smashing the DB with
-                       // large numbers of concurrent invalidations of the same page.
-                       $now = $dbw->timestamp();
-                       $ids = $dbw->selectFieldValues(
-                               'page',
-                               'page_id',
-                               [
-                                       'page_namespace' => $namespace,
-                                       'page_title' => $dbkeys,
-                                       'page_touched < ' . $dbw->addQuotes( $now )
-                               ],
-                               __METHOD__
-                       );
-
-                       if ( !$ids ) {
-                               return;
-                       }
-
-                       $batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
-                       $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
-                       foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
-                               $dbw->update(
+               $dbw->onTransactionIdle(
+                       function () use ( $dbw, $namespace, $dbkeys ) {
+                               $services = MediaWikiServices::getInstance();
+                               $lbFactory = $services->getDBLoadBalancerFactory();
+                               // Determine which pages need to be updated.
+                               // This is necessary to prevent the job queue from smashing the DB with
+                               // large numbers of concurrent invalidations of the same page.
+                               $now = $dbw->timestamp();
+                               $ids = $dbw->selectFieldValues(
                                        'page',
-                                       [ 'page_touched' => $now ],
+                                       'page_id',
                                        [
-                                               'page_id' => $idBatch,
-                                               'page_touched < ' . $dbw->addQuotes( $now ) // handle races
+                                               'page_namespace' => $namespace,
+                                               'page_title' => $dbkeys,
+                                               'page_touched < ' . $dbw->addQuotes( $now )
                                        ],
                                        __METHOD__
                                );
-                               $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
-                       }
-               } );
+
+                               if ( !$ids ) {
+                                       return;
+                               }
+
+                               $batchSize = $services->getMainConfig()->get( 'UpdateRowsPerQuery' );
+                               $ticket = $lbFactory->getEmptyTransactionTicket( __METHOD__ );
+                               foreach ( array_chunk( $ids, $batchSize ) as $idBatch ) {
+                                       $dbw->update(
+                                               'page',
+                                               [ 'page_touched' => $now ],
+                                               [
+                                                       'page_id' => $idBatch,
+                                                       'page_touched < ' . $dbw->addQuotes( $now ) // handle races
+                                               ],
+                                               __METHOD__
+                                       );
+                                       $lbFactory->commitAndWaitForReplication( __METHOD__, $ticket );
+                               }
+                       },
+                       __METHOD__
+               );
        }
 }
index 3a18fc0..f940580 100644 (file)
@@ -444,15 +444,15 @@ class DBConnRef implements IDatabase {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function onTransactionResolution( callable $callback ) {
+       public function onTransactionResolution( callable $callback, $fname = __METHOD__ ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function onTransactionIdle( callable $callback ) {
+       public function onTransactionIdle( callable $callback, $fname = __METHOD__ ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
-       public function onTransactionPreCommitOrIdle( callable $callback ) {
+       public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ ) {
                return $this->__call( __FUNCTION__, func_get_args() );
        }
 
index 026cbc5..9a0ffd5 100644 (file)
@@ -1284,10 +1284,11 @@ interface IDatabase {
         *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_ROLLBACK)
         *
         * @param callable $callback
+        * @param string $fname Caller name
         * @return mixed
         * @since 1.28
         */
-       public function onTransactionResolution( callable $callback );
+       public function onTransactionResolution( callable $callback, $fname = __METHOD__ );
 
        /**
         * Run a callback as soon as there is no transaction pending.
@@ -1306,9 +1307,10 @@ interface IDatabase {
         *   - How the transaction ended (IDatabase::TRIGGER_COMMIT or IDatabase::TRIGGER_IDLE)
         *
         * @param callable $callback
+        * @param string $fname Caller name
         * @since 1.20
         */
-       public function onTransactionIdle( callable $callback );
+       public function onTransactionIdle( callable $callback, $fname = __METHOD__ );
 
        /**
         * Run a callback before the current transaction commits or now if there is none.
@@ -1322,9 +1324,10 @@ interface IDatabase {
         * Updates will execute in the order they were enqueued.
         *
         * @param callable $callback
+        * @param string $fname Caller name
         * @since 1.22
         */
-       public function onTransactionPreCommitOrIdle( callable $callback );
+       public function onTransactionPreCommitOrIdle( callable $callback, $fname = __METHOD__ );
 
        /**
         * Run a callback each time any transaction commits or rolls back
diff --git a/includes/libs/rdbms/defines.php b/includes/libs/rdbms/defines.php
new file mode 100644 (file)
index 0000000..48baa3c
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+
+/**@{
+ * Database related constants
+ */
+define( 'DBO_DEBUG', 1 );
+define( 'DBO_NOBUFFER', 2 );
+define( 'DBO_IGNORE', 4 );
+define( 'DBO_TRX', 8 ); // automatically start transaction on first query
+define( 'DBO_DEFAULT', 16 );
+define( 'DBO_PERSISTENT', 32 );
+define( 'DBO_SYSDBA', 64 ); // for oracle maintenance
+define( 'DBO_DDLMODE', 128 ); // when using schema files: mostly for Oracle
+define( 'DBO_SSL', 256 );
+define( 'DBO_COMPRESS', 512 );
+/**@}*/
+
+/**@{
+ * Valid database indexes
+ * Operation-based indexes
+ */
+define( 'DB_REPLICA', -1 );     # Read from a replica (or only server)
+define( 'DB_MASTER', -2 );    # Write to master (or only server)
+/**@}*/
+
+/**@{
+ * Flags for IDatabase::makeList()
+ * These are also available as Database class constants
+ */
+define( 'LIST_COMMA', 0 );
+define( 'LIST_AND', 1 );
+define( 'LIST_SET', 2 );
+define( 'LIST_NAMES', 3 );
+define( 'LIST_OR', 4 );
+/**@}*/
index feae4bd..107a7e2 100644 (file)
@@ -62,6 +62,11 @@ abstract class LBFactory {
        /** @var callable[] */
        protected $replicationWaitCallbacks = [];
 
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
+
        const SHUTDOWN_NO_CHRONPROT = 0; // don't save DB positions at all
        const SHUTDOWN_CHRONPROT_ASYNC = 1; // save DB positions, but don't wait on remote DCs
        const SHUTDOWN_CHRONPROT_SYNC = 2; // save DB positions, waiting on all DCs
@@ -75,6 +80,7 @@ abstract class LBFactory {
         */
        public function __construct( array $conf ) {
                $this->domain = isset( $conf['domain'] ) ? $conf['domain'] : '';
+
                if ( isset( $conf['readOnlyReason'] ) && is_string( $conf['readOnlyReason'] ) ) {
                        $this->readOnlyReason = $conf['readOnlyReason'];
                }
@@ -91,7 +97,7 @@ abstract class LBFactory {
                $this->errorLogger = isset( $conf['errorLogger'] )
                        ? $conf['errorLogger']
                        : function ( Exception $e ) {
-                               trigger_error( E_WARNING, $e->getMessage() );
+                               trigger_error( E_WARNING, get_class( $e ) . ': ' . $e->getMessage() );
                        };
                $this->hostname = isset( $conf['hostname'] )
                        ? $conf['hostname']
@@ -105,6 +111,8 @@ abstract class LBFactory {
                        : new TransactionProfiler();
 
                $this->ticket = mt_rand();
+               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
+               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
        }
 
        /**
@@ -554,7 +562,7 @@ abstract class LBFactory {
                        isset( $_GET['cpPosTime'] ) ? $_GET['cpPosTime'] : null
                );
                $chronProt->setLogger( $this->replLogger );
-               if ( PHP_SAPI === 'cli' ) {
+               if ( $this->cliMode ) {
                        $chronProt->setEnabled( false );
                }
 
@@ -609,7 +617,9 @@ abstract class LBFactory {
                        'connLogger' => $this->connLogger,
                        'replLogger' => $this->replLogger,
                        'errorLogger' => $this->errorLogger,
-                       'hostname' => $this->hostname
+                       'hostname' => $this->hostname,
+                       'cliMode' => $this->cliMode,
+                       'agent' => $this->agent
                ];
        }
 
@@ -641,4 +651,12 @@ abstract class LBFactory {
        public function closeAll() {
                $this->forEachLBCallMethod( 'closeAll', [] );
        }
+
+       /**
+        * @param string $agent Agent name for query profiling
+        * @since 1.28
+        */
+       public function setAgentName( $agent ) {
+               $this->agent = $agent;
+       }
 }
index cea7523..db69de1 100644 (file)
@@ -45,7 +45,7 @@ class LoadBalancer implements ILoadBalancer {
        /** @var array[] $aliases Map of (table => (dbname, schema, prefix) map) */
        private $tableAliases = [];
 
-       /** @var LoadMonitor */
+       /** @var ILoadMonitor */
        private $mLoadMonitor;
        /** @var BagOStuff */
        private $srvCache;
@@ -88,6 +88,10 @@ class LoadBalancer implements ILoadBalancer {
        private $localDomain;
        /** @var string Current server name */
        private $host;
+       /** @var bool Whether this PHP instance is for a CLI script */
+       protected $cliMode;
+       /** @var string Agent name for query profiling */
+       protected $agent;
 
        /** @var callable Exception logger */
        private $errorLogger;
@@ -175,7 +179,7 @@ class LoadBalancer implements ILoadBalancer {
                $this->errorLogger = isset( $params['errorLogger'] )
                        ? $params['errorLogger']
                        : function ( Exception $e ) {
-                               trigger_error( E_WARNING, $e->getMessage() );
+                               trigger_error( get_class( $e ) . ': ' . $e->getMessage(), E_WARNING );
                        };
 
                foreach ( [ 'replLogger', 'connLogger', 'queryLogger', 'perfLogger' ] as $key ) {
@@ -185,12 +189,14 @@ class LoadBalancer implements ILoadBalancer {
                $this->host = isset( $params['hostname'] )
                        ? $params['hostname']
                        : ( gethostname() ?: 'unknown' );
+               $this->cliMode = isset( $params['cliMode'] ) ? $params['cliMode'] : PHP_SAPI === 'cli';
+               $this->agent = isset( $params['agent'] ) ? $params['agent'] : '';
        }
 
        /**
         * Get a LoadMonitor instance
         *
-        * @return LoadMonitor
+        * @return ILoadMonitor
         */
        private function getLoadMonitor() {
                if ( !isset( $this->mLoadMonitor ) ) {
@@ -810,6 +816,9 @@ class LoadBalancer implements ILoadBalancer {
                $server['connLogger'] = $this->connLogger;
                $server['queryLogger'] = $this->queryLogger;
                $server['trxProfiler'] = $this->trxProfiler;
+               $server['cliMode'] = $this->cliMode;
+               $server['errorLogger'] = $this->errorLogger;
+               $server['agent'] = $this->agent;
 
                // Create a live connection object
                try {
diff --git a/includes/libs/rdbms/loadmonitor/ILoadMonitor.php b/includes/libs/rdbms/loadmonitor/ILoadMonitor.php
new file mode 100644 (file)
index 0000000..e355c03
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Database load monitoring interface
+ *
+ * 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
+ * @ingroup Database
+ */
+use Psr\Log\LoggerAwareInterface;
+
+/**
+ * An interface for database load monitoring
+ *
+ * @ingroup Database
+ */
+interface ILoadMonitor extends LoggerAwareInterface {
+       /**
+        * Construct a new LoadMonitor with a given LoadBalancer parent
+        *
+        * @param ILoadBalancer $lb LoadBalancer this instance serves
+        * @param BagOStuff $sCache Local server memory cache
+        * @param BagOStuff $cCache Local cluster memory cache
+        */
+       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache );
+
+       /**
+        * Perform pre-connection load ratio adjustment.
+        * @param int[] &$loads
+        * @param string|bool $group The selected query group. Default: false
+        * @param string|bool $domain Default: false
+        */
+       public function scaleLoads( &$loads, $group = false, $domain = false );
+
+       /**
+        * Get an estimate of replication lag (in seconds) for each server
+        *
+        * Values may be "false" if replication is too broken to estimate
+        *
+        * @param integer[] $serverIndexes
+        * @param string $domain
+        *
+        * @return array Map of (server index => float|int|bool)
+        */
+       public function getLagTimes( $serverIndexes, $domain );
+
+       /**
+        * Clear any process and persistent cache of lag times
+        * @since 1.27
+        */
+       public function clearCaches();
+}
index 460746b..1da8f4e 100644 (file)
@@ -1,7 +1,5 @@
 <?php
 /**
- * Database load monitoring.
- *
  * 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
  * @file
  * @ingroup Database
  */
-use Psr\Log\LoggerAwareInterface;
+
+use Psr\Log\LoggerInterface;
 
 /**
- * An interface for database load monitoring
+ * Basic DB load monitor with no external dependencies
+ * Uses memcached to cache the replication lag for a short time
  *
  * @ingroup Database
  */
-interface LoadMonitor extends LoggerAwareInterface {
-       /**
-        * Construct a new LoadMonitor with a given LoadBalancer parent
-        *
-        * @param ILoadBalancer $lb LoadBalancer this instance serves
-        * @param BagOStuff $sCache Local server memory cache
-        * @param BagOStuff $cCache Local cluster memory cache
-        */
-       public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache );
-
-       /**
-        * Perform pre-connection load ratio adjustment.
-        * @param int[] &$loads
-        * @param string|bool $group The selected query group. Default: false
-        * @param string|bool $domain Default: false
-        */
-       public function scaleLoads( &$loads, $group = false, $domain = false );
-
-       /**
-        * Get an estimate of replication lag (in seconds) for each server
-        *
-        * Values may be "false" if replication is too broken to estimate
-        *
-        * @param integer[] $serverIndexes
-        * @param string $domain
-        *
-        * @return array Map of (server index => float|int|bool)
-        */
-       public function getLagTimes( $serverIndexes, $domain );
-
-       /**
-        * Clear any process and persistent cache of lag times
-        * @since 1.27
-        */
-       public function clearCaches();
+class LoadMonitor implements ILoadMonitor {
+       /** @var ILoadBalancer */
+       protected $parent;
+       /** @var BagOStuff */
+       protected $srvCache;
+       /** @var BagOStuff */
+       protected $mainCache;
+       /** @var LoggerInterface */
+       protected $replLogger;
+
+       public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) {
+               $this->parent = $lb;
+               $this->srvCache = $srvCache;
+               $this->mainCache = $cache;
+               $this->replLogger = new \Psr\Log\NullLogger();
+       }
+
+       public function setLogger( LoggerInterface $logger ) {
+               $this->replLogger = $logger;
+       }
+
+       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+       }
+
+       public function getLagTimes( $serverIndexes, $domain ) {
+               if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
+                       # Single server only, just return zero without caching
+                       return [ 0 => 0 ];
+               }
+
+               $key = $this->getLagTimeCacheKey();
+               # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
+               $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
+               # Keep keys around longer as fallbacks
+               $staleTTL = 60;
+
+               # (a) Check the local APC cache
+               $value = $this->srvCache->get( $key );
+               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
+                       return $value['lagTimes']; // cache hit
+               }
+               $staleValue = $value ?: false;
+
+               # (b) Check the shared cache and backfill APC
+               $value = $this->mainCache->get( $key );
+               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
+                       $this->srvCache->set( $key, $value, $staleTTL );
+                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
+
+                       return $value['lagTimes']; // cache hit
+               }
+               $staleValue = $value ?: $staleValue;
+
+               # (c) Cache key missing or expired; regenerate and backfill
+               if ( $this->mainCache->lock( $key, 0, 10 ) ) {
+                       # Let this process alone update the cache value
+                       $cache = $this->mainCache;
+                       /** @noinspection PhpUnusedLocalVariableInspection */
+                       $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
+                               $cache->unlock( $key );
+                       } );
+               } elseif ( $staleValue ) {
+                       # Could not acquire lock but an old cache exists, so use it
+                       return $staleValue['lagTimes'];
+               }
+
+               $lagTimes = [];
+               foreach ( $serverIndexes as $i ) {
+                       if ( $i == $this->parent->getWriterIndex() ) {
+                               $lagTimes[$i] = 0; // master always has no lag
+                               continue;
+                       }
+
+                       $conn = $this->parent->getAnyOpenConnection( $i );
+                       if ( $conn ) {
+                               $close = false; // already open
+                       } else {
+                               $conn = $this->parent->openConnection( $i, $domain );
+                               $close = true; // new connection
+                       }
+
+                       if ( !$conn ) {
+                               $lagTimes[$i] = false;
+                               $host = $this->parent->getServerName( $i );
+                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
+                               continue;
+                       }
+
+                       $lagTimes[$i] = $conn->getLag();
+                       if ( $lagTimes[$i] === false ) {
+                               $host = $this->parent->getServerName( $i );
+                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
+                       }
+
+                       if ( $close ) {
+                               # Close the connection to avoid sleeper connections piling up.
+                               # Note that the caller will pick one of these DBs and reconnect,
+                               # which is slightly inefficient, but this only matters for the lag
+                               # time cache miss cache, which is far less common that cache hits.
+                               $this->parent->closeConnection( $conn );
+                       }
+               }
+
+               # Add a timestamp key so we know when it was cached
+               $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
+               $this->mainCache->set( $key, $value, $staleTTL );
+               $this->srvCache->set( $key, $value, $staleTTL );
+               $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
+
+               return $value['lagTimes'];
+       }
+
+       public function clearCaches() {
+               $key = $this->getLagTimeCacheKey();
+               $this->srvCache->delete( $key );
+               $this->mainCache->delete( $key );
+       }
+
+       private function getLagTimeCacheKey() {
+               $writerIndex = $this->parent->getWriterIndex();
+               // Lag is per-server, not per-DB, so key on the master DB name
+               return $this->srvCache->makeGlobalKey(
+                       'lag-times',
+                       $this->parent->getServerName( $writerIndex )
+               );
+       }
 }
index 02babfe..7286417 100644 (file)
  * @ingroup Database
  */
 
-use Psr\Log\LoggerInterface;
-
 /**
  * Basic MySQL load monitor with no external dependencies
  * Uses memcached to cache the replication lag for a short time
  *
  * @ingroup Database
  */
-class LoadMonitorMySQL implements LoadMonitor {
-       /** @var ILoadBalancer */
-       protected $parent;
-       /** @var BagOStuff */
-       protected $srvCache;
-       /** @var BagOStuff */
-       protected $mainCache;
-       /** @var LoggerInterface */
-       protected $replLogger;
-
-       public function __construct( ILoadBalancer $lb, BagOStuff $srvCache, BagOStuff $cache ) {
-               $this->parent = $lb;
-               $this->srvCache = $srvCache;
-               $this->mainCache = $cache;
-               $this->replLogger = new \Psr\Log\NullLogger();
-       }
-
-       public function setLogger( LoggerInterface $logger ) {
-               $this->replLogger = $logger;
-       }
-
-       public function scaleLoads( &$loads, $group = false, $wiki = false ) {
-       }
-
-       public function getLagTimes( $serverIndexes, $wiki ) {
-               if ( count( $serverIndexes ) == 1 && reset( $serverIndexes ) == 0 ) {
-                       # Single server only, just return zero without caching
-                       return [ 0 => 0 ];
-               }
-
-               $key = $this->getLagTimeCacheKey();
-               # Randomize TTLs to reduce stampedes (4.0 - 5.0 sec)
-               $ttl = mt_rand( 4e6, 5e6 ) / 1e6;
-               # Keep keys around longer as fallbacks
-               $staleTTL = 60;
-
-               # (a) Check the local APC cache
-               $value = $this->srvCache->get( $key );
-               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
-                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from local cache" );
-                       return $value['lagTimes']; // cache hit
-               }
-               $staleValue = $value ?: false;
-
-               # (b) Check the shared cache and backfill APC
-               $value = $this->mainCache->get( $key );
-               if ( $value && $value['timestamp'] > ( microtime( true ) - $ttl ) ) {
-                       $this->srvCache->set( $key, $value, $staleTTL );
-                       $this->replLogger->debug( __METHOD__ . ": got lag times ($key) from main cache" );
-
-                       return $value['lagTimes']; // cache hit
-               }
-               $staleValue = $value ?: $staleValue;
-
-               # (c) Cache key missing or expired; regenerate and backfill
-               if ( $this->mainCache->lock( $key, 0, 10 ) ) {
-                       # Let this process alone update the cache value
-                       $cache = $this->mainCache;
-                       /** @noinspection PhpUnusedLocalVariableInspection */
-                       $unlocker = new ScopedCallback( function () use ( $cache, $key ) {
-                               $cache->unlock( $key );
-                       } );
-               } elseif ( $staleValue ) {
-                       # Could not acquire lock but an old cache exists, so use it
-                       return $staleValue['lagTimes'];
-               }
-
-               $lagTimes = [];
-               foreach ( $serverIndexes as $i ) {
-                       if ( $i == $this->parent->getWriterIndex() ) {
-                               $lagTimes[$i] = 0; // master always has no lag
-                               continue;
-                       }
-
-                       $conn = $this->parent->getAnyOpenConnection( $i );
-                       if ( $conn ) {
-                               $close = false; // already open
-                       } else {
-                               $conn = $this->parent->openConnection( $i, $wiki );
-                               $close = true; // new connection
-                       }
-
-                       if ( !$conn ) {
-                               $lagTimes[$i] = false;
-                               $host = $this->parent->getServerName( $i );
-                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is unreachable" );
-                               continue;
-                       }
-
-                       $lagTimes[$i] = $conn->getLag();
-                       if ( $lagTimes[$i] === false ) {
-                               $host = $this->parent->getServerName( $i );
-                               $this->replLogger->error( __METHOD__ . ": host $host (#$i) is not replicating?" );
-                       }
-
-                       if ( $close ) {
-                               # Close the connection to avoid sleeper connections piling up.
-                               # Note that the caller will pick one of these DBs and reconnect,
-                               # which is slightly inefficient, but this only matters for the lag
-                               # time cache miss cache, which is far less common that cache hits.
-                               $this->parent->closeConnection( $conn );
-                       }
-               }
-
-               # Add a timestamp key so we know when it was cached
-               $value = [ 'lagTimes' => $lagTimes, 'timestamp' => microtime( true ) ];
-               $this->mainCache->set( $key, $value, $staleTTL );
-               $this->srvCache->set( $key, $value, $staleTTL );
-               $this->replLogger->info( __METHOD__ . ": re-calculated lag times ($key)" );
-
-               return $value['lagTimes'];
-       }
-
-       public function clearCaches() {
-               $key = $this->getLagTimeCacheKey();
-               $this->srvCache->delete( $key );
-               $this->mainCache->delete( $key );
-       }
-
-       private function getLagTimeCacheKey() {
-               $writerIndex = $this->parent->getWriterIndex();
-               // Lag is per-server, not per-DB, so key on the master DB name
-               return $this->srvCache->makeGlobalKey(
-                       'lag-times',
-                       $this->parent->getServerName( $writerIndex )
-               );
+class LoadMonitorMySQL extends LoadMonitor {
+       public function scaleLoads( &$loads, $group = false, $domain = false ) {
+               // @TODO: maybe use Threads_running/Threads_created ratio to guess load
+               // and Queries/Uptime to guess if a server is warming up the buffer pool
        }
 }
index 1a40b8f..8062001 100644 (file)
@@ -20,7 +20,7 @@
  */
 use Psr\Log\LoggerInterface;
 
-class LoadMonitorNull implements LoadMonitor {
+class LoadMonitorNull implements ILoadMonitor {
        public function __construct( ILoadBalancer $lb, BagOStuff $sCache, BagOStuff $cCache ) {
 
        }
index 6c0c4a8..faac26d 100644 (file)
@@ -3031,10 +3031,13 @@ class WikiPage implements Page, IDBAccessObject {
                $logEntry->setComment( $reason );
                $logid = $logEntry->insert();
 
-               $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $logEntry, $logid ) {
-                       // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
-                       $logEntry->publish( $logid );
-               } );
+               $dbw->onTransactionPreCommitOrIdle(
+                       function () use ( $dbw, $logEntry, $logid ) {
+                               // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
+                               $logEntry->publish( $logid );
+                       },
+                       __METHOD__
+               );
 
                $dbw->endAtomic( __METHOD__ );
 
@@ -3672,7 +3675,8 @@ class WikiPage implements Page, IDBAccessObject {
                                                $cat->refreshCounts();
                                        }
                                }
-                       }
+                       },
+                       __METHOD__
                );
        }
 
index 30fe3ae..4a2f759 100644 (file)
@@ -219,7 +219,11 @@ class ResourceLoaderContext {
         */
        public function msg() {
                return call_user_func_array( 'wfMessage', func_get_args() )
-                       ->inLanguage( $this->getLanguage() );
+                       ->inLanguage( $this->getLanguage() )
+                       // Use a dummy title because there is no real title
+                       // for this endpoint, and the cache won't vary on it
+                       // anyways.
+                       ->title( Title::newFromText( 'Dwimmerlaik' ) );
        }
 
        /**
index 2351efd..3e94460 100644 (file)
@@ -487,9 +487,12 @@ abstract class ResourceLoaderModule implements LoggerAwareInterface {
                                );
 
                                if ( $dbw->trxLevel() ) {
-                                       $dbw->onTransactionResolution( function () use ( &$scopeLock ) {
-                                               ScopedCallback::consume( $scopeLock ); // release after commit
-                                       } );
+                                       $dbw->onTransactionResolution(
+                                               function () use ( &$scopeLock ) {
+                                                       ScopedCallback::consume( $scopeLock ); // release after commit
+                                               },
+                                               __METHOD__
+                                       );
                                }
                        }
                } catch ( Exception $e ) {
index 48604e1..674846d 100644 (file)
@@ -120,10 +120,13 @@ abstract class RevDelList extends RevisionListBase {
                }
 
                $dbw->startAtomic( __METHOD__ );
-               $dbw->onTransactionResolution( function () {
-                       // Release locks on commit or error
-                       $this->releaseItemLocks();
-               } );
+               $dbw->onTransactionResolution(
+                       function () {
+                               // Release locks on commit or error
+                               $this->releaseItemLocks();
+                       },
+                       __METHOD__
+               );
 
                $missing = array_flip( $this->ids );
                $this->clearFileOps();
diff --git a/includes/search/AugmentPageProps.php b/includes/search/AugmentPageProps.php
new file mode 100644 (file)
index 0000000..29bd463
--- /dev/null
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * Augment search result set with values of certain page props.
+ */
+class AugmentPageProps implements ResultSetAugmentor {
+       /**
+        * @var array List of properties.
+        */
+       private $propnames;
+
+       public function __construct( $propnames ) {
+               $this->propnames = $propnames;
+       }
+
+       public function augmentAll( SearchResultSet $resultSet ) {
+               $titles = $resultSet->extractTitles();
+               return PageProps::getInstance()->getProperties( $titles, $this->propnames );
+       }
+}
diff --git a/includes/search/PerRowAugmentor.php b/includes/search/PerRowAugmentor.php
new file mode 100644 (file)
index 0000000..8eb8b17
--- /dev/null
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * Perform augmentation of each row and return composite result,
+ * indexed by ID.
+ */
+class PerRowAugmentor implements ResultSetAugmentor {
+
+       /**
+        * @var ResultAugmentor
+        */
+       private $rowAugmentor;
+
+       /**
+        * PerRowAugmentor constructor.
+        * @param ResultAugmentor $augmentor Per-result augmentor to use.
+        */
+       public function __construct( ResultAugmentor $augmentor ) {
+               $this->rowAugmentor = $augmentor;
+       }
+
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResultSet $resultSet
+        * @return array Data for all results
+        */
+       public function augmentAll( SearchResultSet $resultSet ) {
+               $data = [];
+               foreach ( $resultSet->extractResults() as $result ) {
+                       $id = $result->getTitle()->getArticleID();
+                       if ( !$id ) {
+                               continue;
+                       }
+                       $data[$id] = $this->rowAugmentor->augment( $result );
+               }
+               return $data;
+       }
+}
diff --git a/includes/search/ResultAugmentor.php b/includes/search/ResultAugmentor.php
new file mode 100644 (file)
index 0000000..350b780
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * Augment search results.
+ *
+ */
+interface ResultAugmentor {
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResult $result
+        * @return mixed Data for this result
+        */
+       public function augment( SearchResult $result );
+}
diff --git a/includes/search/ResultSetAugmentor.php b/includes/search/ResultSetAugmentor.php
new file mode 100644 (file)
index 0000000..94710a8
--- /dev/null
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * Augment search results.
+ *
+ */
+interface ResultSetAugmentor {
+       /**
+        * Produce data to augment search result set.
+        * @param SearchResultSet $resultSet
+        * @return array Data for all results
+        */
+       public function augmentAll( SearchResultSet $resultSet );
+}
index c2ccca0..1eba141 100644 (file)
@@ -695,6 +695,37 @@ abstract class SearchEngine {
                Hooks::run( 'SearchIndexFields', [ &$fields, $this ] );
                return $fields;
        }
+
+       /**
+        * Augment search results with extra data.
+        *
+        * @param SearchResultSet $resultSet
+        */
+       public function augmentSearchResults( SearchResultSet $resultSet ) {
+               $setAugmentors = [];
+               $rowAugmentors = [];
+               Hooks::run( "SearchResultsAugment", [ &$setAugmentors, &$rowAugmentors ] );
+
+               if ( !$setAugmentors && !$rowAugmentors ) {
+                       // We're done here
+                       return;
+               }
+
+               // Convert row augmentors to set augmentor
+               foreach ( $rowAugmentors as $name => $row ) {
+                       if ( isset( $setAugmentors[$name] ) ) {
+                               throw new InvalidArgumentException( "Both row and set augmentors are defined for $name" );
+                       }
+                       $setAugmentors[$name] = new PerRowAugmentor( $row );
+               }
+
+               foreach ( $setAugmentors as $name => $augmentor ) {
+                       $data = $augmentor->augmentAll( $resultSet );
+                       if ( $data ) {
+                               $resultSet->setAugmentedData( $name, $data );
+                       }
+               }
+       }
 }
 
 /**
index 6d66707..3141797 100644 (file)
@@ -21,7 +21,7 @@ class SearchNearMatchResultSet extends SearchResultSet {
                        return false;
                }
                $this->fetched = true;
-               return SearchResult::newFromTitle( $this->result );
+               return SearchResult::newFromTitle( $this->result, $this );
        }
 
        public function rewind() {
index 21effbb..50db84b 100644 (file)
@@ -56,15 +56,25 @@ class SearchResult {
         */
        protected $searchEngine;
 
+       /**
+        * A set of extension data.
+        * @var array[]
+        */
+       protected $extensionData;
+
        /**
         * Return a new SearchResult and initializes it with a title.
         *
-        * @param Title $title
+        * @param Title           $title
+        * @param SearchResultSet $parentSet
         * @return SearchResult
         */
-       public static function newFromTitle( $title ) {
+       public static function newFromTitle( $title, SearchResultSet $parentSet = null ) {
                $result = new static();
                $result->initFromTitle( $title );
+               if ( $parentSet ) {
+                       $parentSet->augmentResult( $result );
+               }
                return $result;
        }
 
@@ -250,4 +260,24 @@ class SearchResult {
        function isFileMatch() {
                return false;
        }
+
+       /**
+        * Get the extension data as:
+        * augmentor name => data
+        * @return array[]
+        */
+       public function getExtensionData() {
+               return $this->extensionData;
+       }
+
+       /**
+        * Set extension data for this result.
+        * The data is:
+        * augmentor name => data
+        * @param array[] $extensionData
+        */
+       public function setExtensionData( array $extensionData ) {
+               $this->extensionData = $extensionData;
+       }
+
 }
index 69795e7..978db27 100644 (file)
@@ -42,6 +42,29 @@ class SearchResultSet {
 
        protected $containedSyntax = false;
 
+       /**
+        * Cache of titles.
+        * Lists titles of the result set, in the same order as results.
+        * @var Title[]
+        */
+       private $titles;
+
+       /**
+        * Cache of results - serialization of the result iterator
+        * as an array.
+        * @var SearchResult[]
+        */
+       private $results;
+
+       /**
+        * Set of result's extra data, indexed per result id
+        * and then per data item name.
+        * The structure is:
+        * PAGE_ID => [ augmentor name => data, ... ]
+        * @var array[]
+        */
+       protected $extraData = [];
+
        public function __construct( $containedSyntax = false ) {
                $this->containedSyntax = $containedSyntax;
        }
@@ -147,15 +170,15 @@ class SearchResultSet {
        /**
         * Fetches next search result, or false.
         * STUB
-        *
-        * @return SearchResult
+        * FIXME: refactor as iterator, so we could use nicer interfaces.
+        * @return SearchResult|false
         */
        function next() {
                return false;
        }
 
        /**
-        * Rewind result set back to begining
+        * Rewind result set back to beginning
         */
        function rewind() {
        }
@@ -176,4 +199,69 @@ class SearchResultSet {
        public function searchContainedSyntax() {
                return $this->containedSyntax;
        }
+
+       /**
+        * Extract all the results in the result set as array.
+        * @return SearchResult[]
+        */
+       public function extractResults() {
+               if ( is_null( $this->results ) ) {
+                       $this->results = [];
+                       if ( $this->numRows() == 0 ) {
+                               // Don't bother if we've got empty result
+                               return $this->results;
+                       }
+                       $this->rewind();
+                       while ( ( $result = $this->next() ) != false ) {
+                               $this->results[] = $result;
+                       }
+                       $this->rewind();
+               }
+               return $this->results;
+       }
+
+       /**
+        * Extract all the titles in the result set.
+        * @return Title[]
+        */
+       public function extractTitles() {
+               if ( is_null( $this->titles ) ) {
+                       if ( $this->numRows() == 0 ) {
+                               // Don't bother if we've got empty result
+                               $this->titles = [];
+                       } else {
+                               $this->titles = array_map(
+                                       function ( SearchResult $result ) {
+                                               return $result->getTitle();
+                                       },
+                                       $this->extractResults() );
+                       }
+               }
+               return $this->titles;
+       }
+
+       /**
+        * Sets augmented data for result set.
+        * @param string $name Extra data item name
+        * @param array[] $data Extra data as PAGEID => data
+        */
+       public function setAugmentedData( $name, $data ) {
+               foreach ( $data as $id => $resultData ) {
+                       $this->extraData[$id][$name] = $resultData;
+               }
+       }
+
+       /**
+        * Returns extra data for specific result and store it in SearchResult object.
+        * @param SearchResult $result
+        * @return array|null List of data as name => value or null if none present.
+        */
+       public function augmentResult( SearchResult $result ) {
+               $id = $result->getTitle()->getArticleID();
+               if ( !$id || !isset( $this->extraData[$id] ) ) {
+                       return null;
+               }
+               $result->setExtensionData( $this->extraData[$id] );
+               return $this->extraData[$id];
+       }
 }
index 6b60899..c3985d1 100644 (file)
@@ -37,7 +37,7 @@ class SqlSearchResultSet extends SearchResultSet {
                }
 
                return SearchResult::newFromTitle(
-                       Title::makeTitle( $row->page_namespace, $row->page_title )
+                       Title::makeTitle( $row->page_namespace, $row->page_title ), $this
                );
        }
 
index 26b86f9..6daf19f 100644 (file)
@@ -403,6 +403,7 @@ class SpecialSearch extends SpecialPage {
 
                        // show results
                        if ( $numTextMatches > 0 ) {
+                               $search->augmentSearchResults( $textMatches );
                                $out->addHTML( $this->showMatches( $textMatches ) );
                        }
 
@@ -716,7 +717,7 @@ class SpecialSearch extends SpecialPage {
         *
         * @return string
         */
-       protected function showMatches( &$matches, $interwiki = null ) {
+       protected function showMatches( $matches, $interwiki = null ) {
                global $wgContLang;
 
                $terms = $wgContLang->convertForSearchResult( $matches->termMatches() );
@@ -725,7 +726,7 @@ class SpecialSearch extends SpecialPage {
                $pos = $this->offset;
 
                if ( $result && $interwiki ) {
-                       $out .= $this->interwikiHeader( $interwiki, $result );
+                       $out .= $this->interwikiHeader( $interwiki, $matches );
                }
 
                $out .= "<ul class='mw-search-results'>\n";
@@ -750,7 +751,7 @@ class SpecialSearch extends SpecialPage {
         *
         * @return string
         */
-       protected function showHit( $result, $terms, $position ) {
+       protected function showHit( SearchResult $result, $terms, $position ) {
 
                if ( $result->isBrokenTitle() ) {
                        return '';
index 2af0324..0d06c7b 100644 (file)
@@ -2358,7 +2358,8 @@ class User implements IDBAccessObject {
                        wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
                                function() use ( $cache, $key ) {
                                        $cache->delete( $key );
-                               }
+                               },
+                               __METHOD__
                        );
                }
        }
@@ -4927,9 +4928,12 @@ class User implements IDBAccessObject {
         * Deferred version of incEditCountImmediate()
         */
        public function incEditCount() {
-               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle( function() {
-                       $this->incEditCountImmediate();
-               } );
+               wfGetDB( DB_MASTER )->onTransactionPreCommitOrIdle(
+                       function () {
+                               $this->incEditCountImmediate();
+                       },
+                       __METHOD__
+               );
        }
 
        /**
index 09244a4..69bc503 100644 (file)
@@ -273,15 +273,20 @@ class UserRightsProxy {
         * Replaces User::touchUser()
         */
        function invalidateCache() {
-               $this->db->update( 'user',
+               $this->db->update(
+                       'user',
                        [ 'user_touched' => $this->db->timestamp() ],
                        [ 'user_id' => $this->id ],
-                       __METHOD__ );
+                       __METHOD__
+               );
 
                $wikiId = $this->db->getWikiID();
                $userId = $this->id;
-               $this->db->onTransactionPreCommitOrIdle( function() use ( $wikiId, $userId ) {
-                       User::purge( $wikiId, $userId );
-               } );
+               $this->db->onTransactionPreCommitOrIdle(
+                       function () use ( $wikiId, $userId ) {
+                               User::purge( $wikiId, $userId );
+                       },
+                       __METHOD__
+               );
        }
 }
index 1fbca14..7e0fb45 100644 (file)
@@ -553,8 +553,19 @@ abstract class Maintenance {
         * Set triggers like when to try to run deferred updates
         * @since 1.28
         */
-       public function setTriggers() {
+       public function setAgentAndTriggers() {
+               if ( function_exists( 'posix_getpwuid' ) ) {
+                       $agent = posix_getpwuid( posix_geteuid() )['name'];
+               } else {
+                       $agent = 'sysadmin';
+               }
+               $agent .= '@' . wfHostname();
+
                $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
+               // Add a comment for easy SHOW PROCESSLIST interpretation
+               $lbFactory->setAgentName(
+                       mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent
+               );
                self::setLBFactoryTriggers( $lbFactory );
        }
 
index 890fe45..60b24a2 100644 (file)
@@ -104,7 +104,7 @@ $maintenance->checkRequiredExtensions();
 
 // A good time when no DBs have writes pending is around lag checks.
 // This avoids having long running scripts just OOM and lose all the updates.
-$maintenance->setTriggers();
+$maintenance->setAgentAndTriggers();
 
 // Do the work
 $maintenance->execute();
index 63c3490..89168db 100644 (file)
@@ -166,6 +166,7 @@ return [
                'scripts' => 'resources/lib/jquery/jquery.appear.js',
        ],
        'jquery.arrowSteps' => [
+               'deprecated' => true,
                'scripts' => 'resources/src/jquery/jquery.arrowSteps.js',
                'styles' => 'resources/src/jquery/jquery.arrowSteps.css',
                'targets' => [ 'desktop', 'mobile' ],
index 3b21ff8..7687236 100644 (file)
@@ -20,7 +20,11 @@ class ApiQueryAllPagesTest extends ApiTestCase {
        public function testPrefixNormalizationSearchBug() {
                $title = Title::newFromText( 'Category:Template:xyz' );
                $page = WikiPage::factory( $title );
-               $page->doEdit( 'Some text', 'inserting content' );
+
+               $page->doEditContent(
+                       ContentHandler::makeContent( 'Some text', $page->getTitle() ),
+                       'inserting content'
+               );
 
                $result = $this->doApiRequest( [
                        'action' => 'query',
index a6f22b1..38a1d68 100644 (file)
@@ -15,7 +15,11 @@ class ApiQueryRevisionsTest extends ApiTestCase {
                $pageName = 'Help:' . __METHOD__;
                $title = Title::newFromText( $pageName );
                $page = WikiPage::factory( $title );
-               $page->doEdit( 'Some text', 'inserting content' );
+
+               $page->doEditContent(
+                       ContentHandler::makeContent( 'Some text', $page->getTitle() ),
+                       'inserting content'
+               );
 
                $apiResult = $this->doApiRequest( [
                        'action' => 'query',
index e884640..d4be6e4 100644 (file)
@@ -25,6 +25,7 @@ class DatabaseTest extends MediaWikiTestCase {
                }
                $this->db->restoreFlags( IDatabase::RESTORE_INITIAL );
        }
+
        /**
         * @covers DatabaseBase::dropTable
         */
@@ -231,7 +232,7 @@ class DatabaseTest extends MediaWikiTestCase {
 
        private function dropFunctions() {
                $this->db->query( 'DROP FUNCTION IF EXISTS mw_test_function'
-                               . ( $this->db->getType() == 'postgres' ? '()' : '' )
+                       . ( $this->db->getType() == 'postgres' ? '()' : '' )
                );
        }
 
@@ -247,26 +248,35 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->setFlag( DBO_TRX );
                $called = false;
                $flagSet = null;
-               $db->onTransactionIdle( function() use ( $db, &$flagSet, &$called ) {
-                       $called = true;
-                       $flagSet = $db->getFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet, &$called ) {
+                               $called = true;
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
                $this->assertTrue( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
                $this->assertTrue( $called, 'Callback reached' );
 
                $db->clearFlag( DBO_TRX );
                $flagSet = null;
-               $db->onTransactionIdle( function() use ( $db, &$flagSet ) {
-                       $flagSet = $db->getFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db, &$flagSet ) {
+                               $flagSet = $db->getFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $flagSet, 'DBO_TRX off in callback' );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
 
                $db->clearFlag( DBO_TRX );
-               $db->onTransactionIdle( function() use ( $db ) {
-                       $db->setFlag( DBO_TRX );
-               } );
+               $db->onTransactionIdle(
+                       function () use ( $db ) {
+                               $db->setFlag( DBO_TRX );
+                       },
+                       __METHOD__
+               );
                $this->assertFalse( $db->getFlag( DBO_TRX ), 'DBO_TRX restored to default' );
        }
 
@@ -276,7 +286,7 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->clearFlag( DBO_TRX );
                $db->begin( __METHOD__ );
                $called = false;
-               $db->onTransactionResolution( function() use ( $db, &$called ) {
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
@@ -287,7 +297,7 @@ class DatabaseTest extends MediaWikiTestCase {
                $db->clearFlag( DBO_TRX );
                $db->begin( __METHOD__ );
                $called = false;
-               $db->onTransactionResolution( function() use ( $db, &$called ) {
+               $db->onTransactionResolution( function () use ( $db, &$called ) {
                        $called = true;
                        $db->setFlag( DBO_TRX );
                } );
@@ -302,7 +312,7 @@ class DatabaseTest extends MediaWikiTestCase {
        public function testTransactionListener() {
                $db = $this->db;
 
-               $db->setTransactionListener( 'ping', function() use ( $db, &$called ) {
+               $db->setTransactionListener( 'ping', function () use ( $db, &$called ) {
                        $called = true;
                } );
 
index 10e0f59..e55efee 100644 (file)
@@ -154,6 +154,7 @@ class WikiPageTest extends MediaWikiLangTestCase {
 
        /**
         * @covers WikiPage::doEdit
+        * @deprecated since 1.21. Should be removed when WikiPage::doEdit() gets removed
         */
        public function testDoEdit() {
                $this->hideDeprecated( "WikiPage::doEdit" );
index 0b34b6f..902fc9e 100644 (file)
@@ -207,4 +207,50 @@ class SearchEngineTest extends MediaWikiLangTestCase {
                $this->assertArrayHasKey( 'testData', $mapping );
                $this->assertEquals( 'test', $mapping['testData'] );
        }
+
+       public function hookSearchIndexFields( $mockFieldBuilder, &$fields, SearchEngine $engine ) {
+               $fields['testField'] = $mockFieldBuilder( "testField", SearchIndexField::INDEX_TYPE_TEXT );
+               return true;
+       }
+
+       public function testAugmentorSearch() {
+               $this->search->setNamespaces( [ 0, 1, 4 ] );
+               $resultSet = $this->search->searchText( 'smithee' );
+               // Not using mock since PHPUnit mocks do not work properly with references in params
+               $this->mergeMwGlobalArrayValue( 'wgHooks',
+                       [ 'SearchResultsAugment' => [ [ $this, 'addAugmentors' ] ] ] );
+               $this->search->augmentSearchResults( $resultSet );
+               for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
+                       $id = $result->getTitle()->getArticleID();
+                       $augmentData = "Result:$id:" . $result->getTitle()->getText();
+                       $augmentData2 = "Result2:$id:" . $result->getTitle()->getText();
+                       $this->assertEquals( [ 'testSet' => $augmentData, 'testRow' => $augmentData2 ],
+                               $result->getExtensionData() );
+               }
+       }
+
+       public function addAugmentors( &$setAugmentors, &$rowAugmentors ) {
+               $setAugmentor = $this->getMock( 'ResultSetAugmentor' );
+               $setAugmentor->expects( $this->once() )
+                       ->method( 'augmentAll' )
+                       ->willReturnCallback( function ( SearchResultSet $resultSet ) {
+                               $data = [];
+                               for ( $result = $resultSet->next(); $result; $result = $resultSet->next() ) {
+                                       $id = $result->getTitle()->getArticleID();
+                                       $data[$id] = "Result:$id:" . $result->getTitle()->getText();
+                               }
+                               $resultSet->rewind();
+                               return $data;
+                       } );
+               $setAugmentors['testSet'] = $setAugmentor;
+
+               $rowAugmentor = $this->getMock( 'ResultAugmentor' );
+               $rowAugmentor->expects( $this->exactly( 2 ) )
+                       ->method( 'augment' )
+                       ->willReturnCallback( function ( SearchResult $result ) {
+                               $id = $result->getTitle()->getArticleID();
+                               return "Result2:$id:" . $result->getTitle()->getText();
+                       } );
+               $rowAugmentors['testRow'] = $rowAugmentor;
+       }
 }
index 985554b..34548c0 100644 (file)
@@ -269,7 +269,14 @@ class UserTest extends MediaWikiTestCase {
                // let the user have a few (3) edits
                $page = WikiPage::factory( Title::newFromText( 'Help:UserTest_EditCount' ) );
                for ( $i = 0; $i < 3; $i++ ) {
-                       $page->doEdit( (string)$i, 'test', 0, false, $user );
+
+                       $page->doEditContent(
+                               ContentHandler::makeContent( (string)$i, $page->getTitle() ),
+                               'test',
+                               0,
+                               false,
+                               $user
+                       );
                }
 
                $this->assertEquals(