Merge "EditPage: Make getCheckboxesDefinition() public"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 31 Aug 2017 17:37:35 +0000 (17:37 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 31 Aug 2017 17:37:36 +0000 (17:37 +0000)
16 files changed:
includes/libs/rdbms/database/DatabasePostgres.php
includes/libs/rdbms/exception/DBConnectionError.php
includes/libs/rdbms/exception/DBError.php
includes/libs/rdbms/exception/DBExpectedError.php
includes/libs/rdbms/exception/DBQueryError.php
includes/libs/rdbms/exception/DBTransactionSizeError.php
includes/specials/SpecialWatchlist.php
includes/upload/UploadFromUrl.php
languages/i18n/en.json
languages/i18n/qqq.json
maintenance/postgres/archives/patch-comment-table.sql
maintenance/postgres/tables.sql
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.RcTopSectionWidget.less
resources/src/mediawiki.rcfilters/styles/mw.rcfilters.ui.less
resources/src/mediawiki.rcfilters/ui/mw.rcfilters.ui.RcTopSectionWidget.js
tests/phpunit/includes/CommentStoreTest.php

index bdac06c..fcfd937 100644 (file)
@@ -521,6 +521,10 @@ __INDEXATTR__;
        public function selectSQLText(
                $table, $vars, $conds = '', $fname = __METHOD__, $options = [], $join_conds = []
        ) {
+               if ( is_string( $options ) ) {
+                       $options = [ $options ];
+               }
+
                // Change the FOR UPDATE option as necessary based on the join conditions. Then pass
                // to the parent function to get the actual SQL text.
                // In Postgres when using FOR UPDATE, only the main table and tables that are inner joined
@@ -532,12 +536,28 @@ __INDEXATTR__;
                        $forUpdateKey = array_search( 'FOR UPDATE', $options, true );
                        if ( $forUpdateKey !== false && $join_conds ) {
                                unset( $options[$forUpdateKey] );
+                               $options['FOR UPDATE'] = [];
+
+                               // All tables not in $join_conds are good
+                               foreach ( $table as $alias => $name ) {
+                                       if ( is_numeric( $alias ) ) {
+                                               $alias = $name;
+                                       }
+                                       if ( !isset( $join_conds[$alias] ) ) {
+                                               $options['FOR UPDATE'][] = $alias;
+                                       }
+                               }
 
                                foreach ( $join_conds as $table_cond => $join_cond ) {
                                        if ( 0 === preg_match( '/^(?:LEFT|RIGHT|FULL)(?: OUTER)? JOIN$/i', $join_cond[0] ) ) {
                                                $options['FOR UPDATE'][] = $table_cond;
                                        }
                                }
+
+                               // Quote alias names so $this->tableName() won't mangle them
+                               $options['FOR UPDATE'] = array_map( function ( $name ) use ( $table ) {
+                                       return isset( $table[$name] ) ? $this->addIdentifierQuotes( $name ) : $name;
+                               }, $options['FOR UPDATE'] );
                        }
 
                        if ( isset( $options['ORDER BY'] ) && $options['ORDER BY'] == 'NULL' ) {
index 0091524..91d98dc 100644 (file)
@@ -28,7 +28,7 @@ class DBConnectionError extends DBExpectedError {
         * @param IDatabase $db Object throwing the error
         * @param string $error Error text
         */
-       function __construct( IDatabase $db = null, $error = 'unknown error' ) {
+       public function __construct( IDatabase $db = null, $error = 'unknown error' ) {
                $msg = 'Cannot access the database';
                if ( trim( $error ) != '' ) {
                        $msg .= ": $error";
index d65e2d3..2f7499b 100644 (file)
@@ -36,7 +36,7 @@ class DBError extends Exception {
         * @param IDatabase $db Object which threw the error
         * @param string $error A simple error message to be used for debugging
         */
-       function __construct( IDatabase $db = null, $error ) {
+       public function __construct( IDatabase $db = null, $error ) {
                $this->db = $db;
                parent::__construct( $error );
        }
index 4f65efa..31d8c27 100644 (file)
@@ -36,7 +36,7 @@ class DBExpectedError extends DBError implements MessageSpecifier, ILocalizedExc
        /** @var string[] Message parameters */
        protected $params;
 
-       function __construct( IDatabase $db = null, $error, array $params = [] ) {
+       public function __construct( IDatabase $db = null, $error, array $params = [] ) {
                parent::__construct( $db, $error );
                $this->params = $params;
        }
index 6a4076f..a8ea3ad 100644 (file)
@@ -41,7 +41,7 @@ class DBQueryError extends DBExpectedError {
         * @param string $sql
         * @param string $fname
         */
-       function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
+       public function __construct( IDatabase $db, $error, $errno, $sql, $fname ) {
                if ( $db instanceof Database && $db->wasConnectionError( $errno ) ) {
                        $message = "A connection error occured. \n" .
                                "Query: $sql\n" .
index e45b9f3..d2622e1 100644 (file)
@@ -25,7 +25,7 @@ namespace Wikimedia\Rdbms;
  * @ingroup Database
  */
 class DBTransactionSizeError extends DBTransactionError {
-       function getKey() {
+       public function getKey() {
                return 'transaction-duration-limit-exceeded';
        }
 }
index dcd2ffa..d0d1b61 100644 (file)
@@ -830,7 +830,11 @@ class SpecialWatchlist extends ChangesListSpecialPage {
                                $watchlistHeader .= $this->msg( 'wlheader-enotif' )->parse() . "\n";
                        }
                        if ( $showUpdatedMarker ) {
-                               $watchlistHeader .= $this->msg( 'wlheader-showupdated' )->parse() . "\n";
+                               $watchlistHeader .= $this->msg(
+                                       $this->isStructuredFilterUiEnabled() ?
+                                               'rcfilters-watchlist-showupdated' :
+                                               'wlheader-showupdated'
+                               )->parse() . "\n";
                        }
                }
                $form .= Html::rawElement(
index 7d697a1..f5367bb 100644 (file)
@@ -287,7 +287,7 @@ class UploadFromUrl extends UploadBase {
 
                wfDebugLog( 'fileupload', $status );
                if ( $status->isOK() ) {
-                       wfDebugLog( 'fileupload', 'Download by URL completed successfuly.' );
+                       wfDebugLog( 'fileupload', 'Download by URL completed successfully.' );
                } else {
                        wfDebugLog(
                                'fileupload',
index 816c9be..b280488 100644 (file)
        "rcfilters-liveupdates-button-title-off": "Display new changes as they happen",
        "rcfilters-watchlist-markSeen-button": "Mark all changes as seen",
        "rcfilters-watchlist-editWatchlist-button": "Edit your list of watched pages",
+       "rcfilters-watchlist-showupdated": "Changes to pages you haven't visited since the changes occurred are in <strong>bold</strong>, with solid markers.",
        "rcnotefrom": "Below {{PLURAL:$5|is the change|are the changes}} since <strong>$3, $4</strong> (up to <strong>$1</strong> shown).",
        "rclistfromreset": "Reset date selection",
        "rclistfrom": "Show new changes starting from $2, $3",
index dfe69a9..1105d15 100644 (file)
        "rcfilters-liveupdates-button-title-off": "Title for the button to enable or disable live updates on [[Special:RecentChanges]] when the feature is OFF.",
        "rcfilters-watchlist-markSeen-button": "Label for the button to mark all changes as seen on [[Special:Watchlist]] when using the structured filters interface.",
        "rcfilters-watchlist-editWatchlist-button": "Label for the button to edit the watched pages on [[Special:Watchlist]] when using the structured filters interface.",
+       "rcfilters-watchlist-showupdated": "Message at the top of [[Special:Watchlist]] when the Structured filters are enabled that describes what unseen changes look like.",
        "rcnotefrom": "This message is displayed at [[Special:RecentChanges]] when viewing recentchanges from some specific time.\n\nThe corresponding message is {{msg-mw|Rclistfrom}}.\n\nParameters:\n* $1 - the maximum number of changes that are displayed\n* $2 - (Optional) a date and time\n* $3 - a date\n* $4 - a time\n* $5 - Number of changes are displayed, for use with PLURAL",
        "rclistfromreset": "Used on [[Special:RecentChanges]] to reset a selection of a certain date range.",
        "rclistfrom": "Used on [[Special:RecentChanges]]. Parameters:\n* $1 - (Currently not use) date and time. The date and the time adds to the rclistfrom description.\n* $2 - time. The time adds to the rclistfrom link description (with split of date and time).\n* $3 - date. The date adds to the rclistfrom link description (with split of date and time).\n\nThe corresponding message is {{msg-mw|Rcnotefrom}}.",
index 8f2b3f3..243a3b3 100644 (file)
@@ -21,7 +21,7 @@ CREATE UNIQUE INDEX revcomment_rev ON revision_comment_temp (revcomment_rev);
 
 CREATE TABLE image_comment_temp (
        imgcomment_name       TEXT NOT NULL,
-       imgcomment_comment_id INTEGER NOT NULL,
-       PRIMARY KEY (imgcomment_name, imgcomment_comment_id)
+       imgcomment_description_id INTEGER NOT NULL,
+       PRIMARY KEY (imgcomment_name, imgcomment_description_id)
 );
 CREATE UNIQUE INDEX imgcomment_name ON image_comment_temp (imgcomment_name);
index c7ace89..eea9e68 100644 (file)
@@ -358,8 +358,8 @@ CREATE INDEX img_sha1          ON image (img_sha1);
 
 CREATE TABLE image_comment_temp (
        imgcomment_name       TEXT NOT NULL,
-       imgcomment_comment_id INTEGER NOT NULL,
-       PRIMARY KEY (imgcomment_name, imgcomment_comment_id)
+       imgcomment_description_id INTEGER NOT NULL,
+       PRIMARY KEY (imgcomment_name, imgcomment_description_id)
 );
 CREATE UNIQUE INDEX imgcomment_name ON image_comment_temp (imgcomment_name);
 
index 38f6f28..9d1cc23 100644 (file)
@@ -1,6 +1,17 @@
 .mw-rcfilters-ui-rcTopSectionWidget {
        &-topLinks {
-               width: 100%;
+               &-table {
+                       width: 100%;
+               }
+
+               &-top {
+                       display: block;
+                       width: 100%;
+
+                       .mw-recentchanges-toplinks {
+                               margin-bottom: 0.5em;
+                       }
+               }
        }
 
        &-savedLinks {
index 0bee2f1..f7081af 100644 (file)
@@ -2,6 +2,10 @@
        &-table {
                display: table;
                width: 100%;
+
+               &-placeholder {
+                       width: 100%;
+               }
        }
 
        &-row {
index 706c888..f0e1241 100644 (file)
        mw.rcfilters.ui.RcTopSectionWidget = function MwRcfiltersUiRcTopSectionWidget(
                savedLinksListWidget, $topLinks, config
        ) {
-               var topLinksCookieName = 'rcfilters-toplinks-collapsed-state',
+               var toplinksTitle,
+                       topLinksCookieName = 'rcfilters-toplinks-collapsed-state',
                        topLinksCookie = mw.cookie.get( topLinksCookieName ),
                        topLinksCookieValue = topLinksCookie || 'collapsed',
-                       toplinksTitle;
+                       widget = this;
+
                config = config || {};
 
                // Parent
                mw.rcfilters.ui.RcTopSectionWidget.parent.call( this, config );
 
+               this.$topLinks = $topLinks;
+
                toplinksTitle = new OO.ui.ButtonWidget( {
                        framed: false,
                        indicator: topLinksCookieValue === 'collapsed' ? 'down' : 'up',
@@ -28,7 +32,7 @@
                        label: $( '<span>' ).append( mw.message( 'rcfilters-other-review-tools' ).parse() ).contents()
                } );
 
-               $topLinks
+               this.$topLinks
                        .addClass( 'mw-rcfilters-ui-ready' )
                        .makeCollapsible( {
                                collapsed: topLinksCookieValue === 'collapsed',
                        .on( 'beforeExpand.mw-collapsible', function () {
                                mw.cookie.set( topLinksCookieName, 'expanded' );
                                toplinksTitle.setIndicator( 'up' );
+                               widget.switchTopLinks( 'expanded' );
                        } )
                        .on( 'beforeCollapse.mw-collapsible', function () {
                                mw.cookie.set( topLinksCookieName, 'collapsed' );
                                toplinksTitle.setIndicator( 'down' );
+                               widget.switchTopLinks( 'collapsed' );
                        } );
 
-               $topLinks.find( '.mw-recentchanges-toplinks-title' ).replaceWith( toplinksTitle.$element );
+               this.$topLinks.find( '.mw-recentchanges-toplinks-title' ).replaceWith( toplinksTitle.$element );
+
+               // Create two positions for the toplinks to toggle between
+               // in the table (first cell) or up above it
+               this.$top = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-topLinks-top' );
+               this.$tableTopLinks = $( '<div>' )
+                       .addClass( 'mw-rcfilters-ui-cell' )
+                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-topLinks-table' );
 
+               // Initialize
                this.$element
                        .addClass( 'mw-rcfilters-ui-rcTopSectionWidget' )
-                       .addClass( 'mw-rcfilters-ui-table' )
                        .append(
+                               this.$top,
                                $( '<div>' )
-                                       .addClass( 'mw-rcfilters-ui-row' )
+                                       .addClass( 'mw-rcfilters-ui-table' )
                                        .append(
                                                $( '<div>' )
-                                                       .addClass( 'mw-rcfilters-ui-cell' )
-                                                       .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-topLinks' )
-                                                       .append( $topLinks )
-                                       )
-                                       .append(
-                                               !mw.user.isAnon() ?
-                                                       $( '<div>' )
-                                                               .addClass( 'mw-rcfilters-ui-cell' )
-                                                               .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-savedLinks' )
-                                                               .append( savedLinksListWidget.$element ) :
-                                                       null
+                                                       .addClass( 'mw-rcfilters-ui-row' )
+                                                       .append(
+                                                               this.$tableTopLinks,
+                                                               $( '<div>' )
+                                                                       .addClass( 'mw-rcfilters-ui-table-placeholder' )
+                                                                       .addClass( 'mw-rcfilters-ui-cell' ),
+                                                               !mw.user.isAnon() ?
+                                                                       $( '<div>' )
+                                                                               .addClass( 'mw-rcfilters-ui-cell' )
+                                                                               .addClass( 'mw-rcfilters-ui-rcTopSectionWidget-savedLinks' )
+                                                                               .append( savedLinksListWidget.$element ) :
+                                                                       null
+                                                       )
                                        )
                        );
+
+               // Initialize top links position
+               widget.switchTopLinks( topLinksCookieValue );
        };
 
        /* Initialization */
 
        OO.inheritClass( mw.rcfilters.ui.RcTopSectionWidget, OO.ui.Widget );
+
+       /**
+        * Switch the top links widget from inside the table (when collapsed)
+        * to the 'top' (when open)
+        *
+        * @param {string} [state] The state of the top links widget: 'expanded' or 'collapsed'
+        */
+       mw.rcfilters.ui.RcTopSectionWidget.prototype.switchTopLinks = function ( state ) {
+               state = state || 'expanded';
+
+               if ( state === 'expanded' ) {
+                       this.$top.append( this.$topLinks );
+               } else {
+                       this.$tableTopLinks.append( this.$topLinks );
+               }
+       };
 }( mediaWiki ) );
index 6dd0925..b65136a 100644 (file)
@@ -363,6 +363,7 @@ class CommentStoreTest extends MediaWikiLangTestCase {
                                $this->assertArrayNotHasKey( "{$key}_id", $fields, "new field, stage=$writeStage" );
                        }
 
+                       $extraFields[$pk] = $this->db->nextSequenceValue( "{$table}_{$pk}_seq" );
                        $this->db->insert( $table, $extraFields + $fields, __METHOD__ );
                        $id = $this->db->insertId();
                        if ( $usesTemp ) {
@@ -404,17 +405,25 @@ class CommentStoreTest extends MediaWikiLangTestCase {
        }
 
        public static function provideInsertRoundTrip() {
+               $db = wfGetDB( DB_REPLICA ); // for timestamps
+
                $msgComment = new Message( 'parentheses', [ 'message comment' ] );
                $textCommentMsg = new RawMessage( '$1', [ 'text comment' ] );
                $nestedMsgComment = new Message( [ 'parentheses', 'rawmessage' ], [ new Message( 'mainpage' ) ] );
                $ipbfields = [
                        'ipb_range_start' => '',
                        'ipb_range_end' => '',
+                       'ipb_by' => 0,
+                       'ipb_timestamp' => $db->timestamp(),
+                       'ipb_expiry' => $db->getInfinity(),
                ];
                $revfields = [
                        'rev_page' => 42,
                        'rev_text_id' => 42,
                        'rev_len' => 0,
+                       'rev_user' => 0,
+                       'rev_user_text' => '',
+                       'rev_timestamp' => $db->timestamp(),
                ];
                $comStoreComment = new CommentStoreComment(
                        null, 'comment store comment', null, [ 'foo' => 'bar' ]