Merge "[MCR] fix RevisionStore::checkDatabaseWikiId for DB names with dashes."
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 4 Jan 2018 17:27:45 +0000 (17:27 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 4 Jan 2018 17:27:45 +0000 (17:27 +0000)
27 files changed:
RELEASE-NOTES-1.31
composer.json
includes/GlobalFunctions.php
includes/api/ApiComparePages.php
includes/api/i18n/en.json
includes/api/i18n/qqq.json
includes/filerepo/RepoGroup.php
includes/htmlform/fields/HTMLTextAreaField.php
includes/installer/PostgresUpdater.php
includes/libs/rdbms/exception/DBError.php
includes/shell/Command.php
includes/shell/Shell.php
includes/specials/SpecialExpandTemplates.php
includes/widget/ComplexNamespaceInputWidget.php
includes/widget/ComplexTitleInputWidget.php
includes/widget/DateInputWidget.php
includes/widget/NamespaceInputWidget.php
includes/widget/SearchInputWidget.php
includes/widget/SelectWithInputWidget.php
includes/widget/TitleInputWidget.php
includes/widget/UserInputWidget.php
includes/widget/UsersMultiselectWidget.php
resources/src/mediawiki.widgets/mw.widgets.DateInputWidget.js
resources/src/mediawiki/htmlform/multiselect.js
tests/phpunit/includes/api/ApiClearHasMsgTest.php
tests/phpunit/includes/api/ApiComparePagesTest.php
tests/phpunit/includes/shell/CommandTest.php

index a496b02..4eb4c01 100644 (file)
@@ -42,6 +42,8 @@ production.
 
 ==== Upgraded external libraries ====
 * Updated jquery.chosen from v0.9.14 to v1.8.2.
+* Updated composer/spdx-licenses from 1.1.4 to
+  1.2.0 (development dependency).
 * …
 
 ==== New external libraries ====
index ee050d5..6b3e8f7 100644 (file)
@@ -49,7 +49,7 @@
                "zordius/lightncandy": "0.23"
        },
        "require-dev": {
-               "composer/spdx-licenses": "1.1.4",
+               "composer/spdx-licenses": "1.2.0",
                "hamcrest/hamcrest-php": "^2.0",
                "jakub-onderka/php-parallel-lint": "0.9.2",
                "jetbrains/phpstorm-stubs": "dev-master#1b9906084d6635456fcf3f3a01f0d7d5b99a578a",
index 523a0f9..310adeb 100644 (file)
@@ -2910,7 +2910,7 @@ function wfGetLBFactory() {
  * Find a file.
  * Shortcut for RepoGroup::singleton()->findFile()
  *
- * @param string $title String or Title object
+ * @param string|Title $title String or Title object
  * @param array $options Associative array of options (see RepoGroup::findFile)
  * @return File|bool File, or false if the file does not exist
  */
index eb67bab..5486594 100644 (file)
@@ -94,6 +94,26 @@ class ApiComparePages extends ApiBase {
                        $this->dieWithError( 'apierror-baddiff' );
                }
 
+               // Extract sections, if told to
+               if ( isset( $params['fromsection'] ) ) {
+                       $fromContent = $fromContent->getSection( $params['fromsection'] );
+                       if ( !$fromContent ) {
+                               $this->dieWithError(
+                                       [ 'apierror-compare-nosuchfromsection', wfEscapeWikiText( $params['fromsection'] ) ],
+                                       'nosuchfromsection'
+                               );
+                       }
+               }
+               if ( isset( $params['tosection'] ) ) {
+                       $toContent = $toContent->getSection( $params['tosection'] );
+                       if ( !$toContent ) {
+                               $this->dieWithError(
+                                       [ 'apierror-compare-nosuchtosection', wfEscapeWikiText( $params['tosection'] ) ],
+                                       'nosuchtosection'
+                               );
+                       }
+               }
+
                // Get the diff
                $context = new DerivativeContext( $this->getContext() );
                if ( $relRev && $relRev->getTitle() ) {
@@ -444,6 +464,7 @@ class ApiComparePages extends ApiBase {
                        'text' => [
                                ApiBase::PARAM_TYPE => 'text'
                        ],
+                       'section' => null,
                        'pst' => false,
                        'contentformat' => [
                                ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
index e1360c8..cceed01 100644 (file)
@@ -64,6 +64,7 @@
        "apihelp-compare-param-fromid": "First page ID to compare.",
        "apihelp-compare-param-fromrev": "First revision to compare.",
        "apihelp-compare-param-fromtext": "Use this text instead of the content of the revision specified by <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>.",
+       "apihelp-compare-param-fromsection": "Only use the specified section of the specified 'from' content.",
        "apihelp-compare-param-frompst": "Do a pre-save transform on <var>fromtext</var>.",
        "apihelp-compare-param-fromcontentmodel": "Content model of <var>fromtext</var>. If not supplied, it will be guessed based on the other parameters.",
        "apihelp-compare-param-fromcontentformat": "Content serialization format of <var>fromtext</var>.",
@@ -72,6 +73,7 @@
        "apihelp-compare-param-torev": "Second revision to compare.",
        "apihelp-compare-param-torelative": "Use a revision relative to the revision determined from <var>fromtitle</var>, <var>fromid</var> or <var>fromrev</var>. All of the other 'to' options will be ignored.",
        "apihelp-compare-param-totext": "Use this text instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.",
+       "apihelp-compare-param-tosection": "Only use the specified section of the specified 'to' content.",
        "apihelp-compare-param-topst": "Do a pre-save transform on <var>totext</var>.",
        "apihelp-compare-param-tocontentmodel": "Content model of <var>totext</var>. If not supplied, it will be guessed based on the other parameters.",
        "apihelp-compare-param-tocontentformat": "Content serialization format of <var>totext</var>.",
        "apierror-chunk-too-small": "Minimum chunk size is $1 {{PLURAL:$1|byte|bytes}} for non-final chunks.",
        "apierror-cidrtoobroad": "$1 CIDR ranges broader than /$2 are not accepted.",
        "apierror-compare-no-title": "Cannot pre-save transform without a title. Try specifying <var>fromtitle</var> or <var>totitle</var>.",
+       "apierror-compare-nosuchfromsection": "There is no section $1 in the 'from' content.",
+       "apierror-compare-nosuchtosection": "There is no section $1 in the 'to' content.",
        "apierror-compare-relative-to-nothing": "No 'from' revision for <var>torelative</var> to be relative to.",
        "apierror-contentserializationexception": "Content serialization failed: $1",
        "apierror-contenttoobig": "The content you supplied exceeds the article size limit of $1 {{PLURAL:$1|kilobyte|kilobytes}}.",
index 1724fa9..d21f29c 100644 (file)
@@ -68,6 +68,7 @@
        "apihelp-compare-param-fromid": "{{doc-apihelp-param|compare|fromid}}",
        "apihelp-compare-param-fromrev": "{{doc-apihelp-param|compare|fromrev}}",
        "apihelp-compare-param-fromtext": "{{doc-apihelp-param|compare|fromtext}}",
+       "apihelp-compare-param-fromsection": "{{doc-apihelp-param|compare|fromsection}}",
        "apihelp-compare-param-frompst": "{{doc-apihelp-param|compare|frompst}}",
        "apihelp-compare-param-fromcontentmodel": "{{doc-apihelp-param|compare|fromcontentmodel}}",
        "apihelp-compare-param-fromcontentformat": "{{doc-apihelp-param|compare|fromcontentformat}}",
@@ -76,6 +77,7 @@
        "apihelp-compare-param-torev": "{{doc-apihelp-param|compare|torev}}",
        "apihelp-compare-param-torelative": "{{doc-apihelp-param|compare|torelative}}",
        "apihelp-compare-param-totext": "{{doc-apihelp-param|compare|totext}}",
+       "apihelp-compare-param-tosection": "{{doc-apihelp-param|compare|tosection}}",
        "apihelp-compare-param-topst": "{{doc-apihelp-param|compare|topst}}",
        "apihelp-compare-param-tocontentmodel": "{{doc-apihelp-param|compare|tocontentmodel}}",
        "apihelp-compare-param-tocontentformat": "{{doc-apihelp-param|compare|tocontentformat}}",
        "apierror-chunk-too-small": "{{doc-apierror}}\n\nParameters:\n* $1 - Minimum size in bytes.",
        "apierror-cidrtoobroad": "{{doc-apierror}}\n\nParameters:\n* $1 - \"IPv4\" or \"IPv6\"\n* $2 - Minimum CIDR mask length.",
        "apierror-compare-no-title": "{{doc-apierror}}",
+       "apierror-compare-nosuchfromsection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
+       "apierror-compare-nosuchtosection": "{{doc-apierror}}\n\nParameters:\n* $1 - Section identifier. Probably a number or \"T-\" followed by a number.",
        "apierror-compare-relative-to-nothing": "{{doc-apierror}}",
        "apierror-contentserializationexception": "{{doc-apierror}}\n\nParameters:\n* $1 - Exception text, may end with punctuation. Currently this is probably English, hopefully we'll fix that in the future.",
        "apierror-contenttoobig": "{{doc-apierror}}\n\nParameters:\n* $1 - Maximum article size in kilobytes.",
index 5e37d67..24d1ab2 100644 (file)
@@ -138,7 +138,7 @@ class RepoGroup {
                $dbkey = $title->getDBkey();
                if ( empty( $options['ignoreRedirect'] )
                        && empty( $options['private'] )
-                       && empty( $options['bypassCache'] )
+                       && empty( $options['latest'] )
                ) {
                        $time = isset( $options['time'] ) ? $options['time'] : '';
                        if ( $this->cache->has( $dbkey, $time, 60 ) ) {
index 466a251..3370e4a 100644 (file)
@@ -5,12 +5,14 @@ class HTMLTextAreaField extends HTMLFormField {
        const DEFAULT_ROWS = 25;
 
        protected $mPlaceholder = '';
+       protected $mUseEditFont = false;
 
        /**
         * @param array $params
         *   - cols, rows: textarea size
         *   - placeholder/placeholder-message: set HTML placeholder attribute
         *   - spellcheck: set HTML spellcheck attribute
+        *   - useeditfont: add CSS classes to use the same font as the wikitext editor
         */
        public function __construct( $params ) {
                parent::__construct( $params );
@@ -20,6 +22,10 @@ class HTMLTextAreaField extends HTMLFormField {
                } elseif ( isset( $params['placeholder'] ) ) {
                        $this->mPlaceholder = $params['placeholder'];
                }
+
+               if ( isset( $params['useeditfont'] ) ) {
+                       $this->mUseEditFont = $params['useeditfont'];
+               }
        }
 
        public function getCols() {
@@ -40,6 +46,8 @@ class HTMLTextAreaField extends HTMLFormField {
        }
 
        public function getInputHTML( $value ) {
+               $classes = [];
+
                $attribs = [
                                'id' => $this->mID,
                                'cols' => $this->getCols(),
@@ -48,11 +56,25 @@ class HTMLTextAreaField extends HTMLFormField {
                        ] + $this->getTooltipAndAccessKey();
 
                if ( $this->mClass !== '' ) {
-                       $attribs['class'] = $this->mClass;
+                       array_push( $classes, $this->mClass );
+               }
+               if ( $this->mUseEditFont ) {
+                       // The following classes can be used here:
+                       // * mw-editfont-monospace
+                       // * mw-editfont-sans-serif
+                       // * mw-editfont-serif
+                       array_push(
+                               $classes,
+                               'mw-editfont-' . $this->mParent->getUser()->getOption( 'editfont' )
+                       );
+                       $this->mParent->getOutput()->addModuleStyles( 'mediawiki.editfont.styles' );
                }
                if ( $this->mPlaceholder !== '' ) {
                        $attribs['placeholder'] = $this->mPlaceholder;
                }
+               if ( count( $classes ) ) {
+                       $attribs['class'] = implode( ' ', $classes );
+               }
 
                $allowedParams = [
                        'tabindex',
@@ -67,6 +89,8 @@ class HTMLTextAreaField extends HTMLFormField {
        }
 
        function getInputOOUI( $value ) {
+               $classes = [];
+
                if ( isset( $this->mParams['cols'] ) ) {
                        throw new Exception( "OOUIHTMLForm does not support the 'cols' parameter for textareas" );
                }
@@ -74,11 +98,25 @@ class HTMLTextAreaField extends HTMLFormField {
                $attribs = $this->getTooltipAndAccessKeyOOUI();
 
                if ( $this->mClass !== '' ) {
-                       $attribs['classes'] = [ $this->mClass ];
+                       array_push( $classes, $this->mClass );
+               }
+               if ( $this->mUseEditFont ) {
+                       // The following classes can be used here:
+                       // * mw-editfont-monospace
+                       // * mw-editfont-sans-serif
+                       // * mw-editfont-serif
+                       array_push(
+                               $classes,
+                               'mw-editfont-' . $this->mParent->getUser()->getOption( 'editfont' )
+                       );
+                       $this->mParent->getOutput()->addModuleStyles( 'mediawiki.editfont.styles' );
                }
                if ( $this->mPlaceholder !== '' ) {
                        $attribs['placeholder'] = $this->mPlaceholder;
                }
+               if ( count( $classes ) ) {
+                       $attribs['classes'] = $classes;
+               }
 
                $allowedParams = [
                        'tabindex',
index e920fb7..fe8a1b1 100644 (file)
@@ -487,7 +487,7 @@ class PostgresUpdater extends DatabaseUpdater {
                        // 1.31
                        [ 'addTable', 'slots', 'patch-slots-table.sql' ],
                        [ 'addTable', 'content', 'patch-content-table.sql' ],
-                       [ 'addTable', 'content_moddels', 'patch-content_models-table.sql' ],
+                       [ 'addTable', 'content_models', 'patch-content_models-table.sql' ],
                        [ 'addTable', 'slot_roles', 'patch-slot_roles-table.sql' ],
                ];
        }
index 2f7499b..5023800 100644 (file)
 
 namespace Wikimedia\Rdbms;
 
-use Exception;
+use RuntimeException;
 
 /**
  * Database error base class
  * @ingroup Database
  */
-class DBError extends Exception {
+class DBError extends RuntimeException {
        /** @var IDatabase|null */
        public $db;
 
index f887ada..4f65e4d 100644 (file)
@@ -56,6 +56,9 @@ class Command {
        /** @var string */
        private $method;
 
+       /** @var string|null */
+       private $inputString;
+
        /** @var bool */
        private $doIncludeStderr = false;
 
@@ -189,6 +192,18 @@ class Command {
                return $this;
        }
 
+       /**
+        * Sends the provided input to the command.
+        * When set to null (default), the command will use the standard input.
+        * @param string|null $inputString
+        * @return $this
+        */
+       public function input( $inputString ) {
+               $this->inputString = is_null( $inputString ) ? null : (string)$inputString;
+
+               return $this;
+       }
+
        /**
         * Controls whether stderr should be included in stdout, including errors from limit.sh.
         * Default: don't include.
@@ -350,7 +365,7 @@ class Command {
                }
 
                $desc = [
-                       0 => [ 'file', 'php://stdin', 'r' ],
+                       0 => $this->inputString === null ? [ 'file', 'php://stdin', 'r' ] : [ 'pipe', 'r' ],
                        1 => [ 'pipe', 'w' ],
                        2 => [ 'pipe', 'w' ],
                ];
@@ -364,8 +379,13 @@ class Command {
                        $this->logger->error( "proc_open() failed: {command}", [ 'command' => $cmd ] );
                        throw new ProcOpenError();
                }
-               $outBuffer = $logBuffer = '';
-               $errBuffer = null;
+
+               $buffers = [
+                       0 => $this->inputString, // input
+                       1 => '', // stdout
+                       2 => null, // stderr
+                       3 => '', // log
+               ];
                $emptyArray = [];
                $status = false;
                $logMsg = false;
@@ -399,8 +419,6 @@ class Command {
                                }
                        }
 
-                       $readyPipes = $pipes;
-
                        // clear get_last_error without actually raising an error
                        // from http://php.net/manual/en/function.error-get-last.php#113518
                        // TODO replace with clear_last_error when requirements are bumped to PHP7
@@ -410,8 +428,17 @@ class Command {
                        @trigger_error( '' );
                        restore_error_handler();
 
+                       $readPipes = wfArrayFilterByKey( $pipes, function ( $fd ) use ( $desc ) {
+                               return $desc[$fd][0] === 'pipe' && $desc[$fd][1] === 'r';
+                       } );
+                       $writePipes = wfArrayFilterByKey( $pipes, function ( $fd ) use ( $desc ) {
+                               return $desc[$fd][0] === 'pipe' && $desc[$fd][1] === 'w';
+                       } );
+                       // stream_select parameter names are from the POV of us being able to do the operation;
+                       // proc_open desriptor types are from the POV of the process doing it.
+                       // So $writePipes is passed as the $read parameter and $readPipes as $write.
                        // phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
-                       $numReadyPipes = @stream_select( $readyPipes, $emptyArray, $emptyArray, $timeout );
+                       $numReadyPipes = @stream_select( $writePipes, $readPipes, $emptyArray, $timeout );
                        if ( $numReadyPipes === false ) {
                                $error = error_get_last();
                                if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
@@ -422,31 +449,36 @@ class Command {
                                        break;
                                }
                        }
-                       foreach ( $readyPipes as $fd => $pipe ) {
-                               $block = fread( $pipe, 65536 );
-                               if ( $block === '' ) {
+                       foreach ( $writePipes + $readPipes as $fd => $pipe ) {
+                               // True if a pipe is unblocked for us to write into, false if for reading from
+                               $isWrite = array_key_exists( $fd, $readPipes );
+
+                               if ( $isWrite ) {
+                                       $res = fwrite( $pipe, $buffers[$fd], 65536 );
+                               } else {
+                                       $res = fread( $pipe, 65536 );
+                               }
+
+                               if ( $res === false ) {
+                                       $logMsg = 'Error ' . ( $isWrite ? 'writing to' : 'reading from' ) . ' pipe';
+                                       break 2;
+                               }
+
+                               if ( $res === '' || $res === 0 ) {
                                        // End of file
                                        fclose( $pipes[$fd] );
                                        unset( $pipes[$fd] );
                                        if ( !$pipes ) {
                                                break 2;
                                        }
-                               } elseif ( $block === false ) {
-                                       // Read error
-                                       $logMsg = "Error reading from pipe";
-                                       break 2;
-                               } elseif ( $fd == 1 ) {
-                                       // From stdout
-                                       $outBuffer .= $block;
-                               } elseif ( $fd == 2 ) {
-                                       // From stderr
-                                       $errBuffer .= $block;
-                               } elseif ( $fd == 3 ) {
-                                       // From log FD
-                                       $logBuffer .= $block;
-                                       if ( strpos( $block, "\n" ) !== false ) {
-                                               $lines = explode( "\n", $logBuffer );
-                                               $logBuffer = array_pop( $lines );
+                               } elseif ( $isWrite ) {
+                                       $buffers[$fd] = substr( $buffers[$fd], $res );
+                               } else {
+                                       $buffers[$fd] .= $res;
+                                       if ( $fd === 3 && strpos( $res, "\n" ) !== false ) {
+                                               // For the log FD, every line is a separate log entry.
+                                               $lines = explode( "\n", $buffers[3] );
+                                               $buffers[3] = array_pop( $lines );
                                                foreach ( $lines as $line ) {
                                                        $this->logger->info( $line );
                                                }
@@ -491,15 +523,15 @@ class Command {
                        $this->logger->warning( "$logMsg: {command}", [ 'command' => $cmd ] );
                }
 
-               if ( $errBuffer && $this->doLogStderr ) {
+               if ( $buffers[2] && $this->doLogStderr ) {
                        $this->logger->error( "Error running {command}: {error}", [
                                'command' => $cmd,
-                               'error' => $errBuffer,
+                               'error' => $buffers[2],
                                'exitcode' => $retval,
                                'exception' => new Exception( 'Shell error' ),
                        ] );
                }
 
-               return new Result( $retval, $outBuffer, $errBuffer );
+               return new Result( $retval, $buffers[1], $buffers[2] );
        }
 }
index 05463db..d57bf4f 100644 (file)
@@ -31,6 +31,7 @@ use MediaWiki\MediaWikiServices;
  *
  * Use call chaining with this class for expressiveness:
  *  $result = Shell::command( 'some command' )
+ *       ->input( 'foo' )
  *       ->environment( [ 'ENVIRONMENT_VARIABLE' => 'VALUE' ] )
  *       ->limits( [ 'time' => 300 ] )
  *       ->execute();
index 35344aa..970c903 100644 (file)
@@ -172,11 +172,7 @@ class SpecialExpandTemplates extends SpecialPage {
                                'rows' => 10,
                                'default' => $input,
                                'id' => 'input',
-                               // The following classes can be used here:
-                               // * mw-editfont-monospace
-                               // * mw-editfont-sans-serif
-                               // * mw-editfont-serif
-                               'cssclass' => 'mw-editfont-' . $this->getUser()->getOption( 'editfont' ),
+                               'useeditfont' => true,
                        ],
                        'removecomments' => [
                                'type' => 'check',
@@ -208,8 +204,6 @@ class SpecialExpandTemplates extends SpecialPage {
                        ],
                ];
 
-               $this->getOutput()->addModuleStyles( 'mediawiki.editfont.styles' );
-
                $form = HTMLForm::factory( 'ooui', $fields, $this->getContext() );
                $form
                        ->setSubmitTextMsg( 'expand_templates_ok' )
index 1d83f51..cdcdd24 100644 (file)
@@ -113,6 +113,7 @@ class ComplexNamespaceInputWidget extends \OOUI\Widget {
                                )
                        )
                );
+               $config['namespace']['dropdown']['$overlay'] = true;
                return parent::getConfig( $config );
        }
 }
index 912537a..aec6619 100644 (file)
@@ -60,7 +60,9 @@ class ComplexTitleInputWidget extends \OOUI\Widget {
 
        public function getConfig( &$config ) {
                $config['namespace'] = $this->config['namespace'];
+               $config['namespace']['dropdown']['$overlay'] = true;
                $config['title'] = $this->config['title'];
+               $config['title']['$overlay'] = true;
                return parent::getConfig( $config );
        }
 }
index b516331..0231cac 100644 (file)
@@ -25,7 +25,6 @@ class DateInputWidget extends \OOUI\TextInputWidget {
        protected $precision = null;
        protected $mustBeAfter = null;
        protected $mustBeBefore = null;
-       protected $overlay = null;
 
        /**
         * @param array $config Configuration options
@@ -52,11 +51,6 @@ class DateInputWidget extends \OOUI\TextInputWidget {
         *   In the 'YYYY-MM-DD' or 'YYYY-MM' format, depending on `precision`.
         * @param string $config['mustBeBefore'] Validates the date to be before this.
         *   In the 'YYYY-MM-DD' or 'YYYY-MM' format, depending on `precision`.
-        * @param string $config['overlay'] The jQuery selector for the overlay layer on which to render
-        *   the calendar. This configuration is useful in cases where the expanded calendar is larger
-        *   than its container. The specified overlay layer is usually on top of the container and has
-        *   a larger area. Applicable only if the widget is infused. By default, the calendar uses
-        *   relative positioning.
         */
        public function __construct( array $config = [] ) {
                $config = array_merge( [
@@ -90,9 +84,6 @@ class DateInputWidget extends \OOUI\TextInputWidget {
                if ( isset( $config['placeholderLabel'] ) ) {
                        $this->placeholderLabel = $config['placeholderLabel'];
                }
-               if ( isset( $config['overlay'] ) ) {
-                       $this->overlay = $config['overlay'];
-               }
 
                // Set up placeholder text visible if the browser doesn't override it (logic taken from JS)
                if ( $this->placeholderDateFormat !== null ) {
@@ -159,9 +150,7 @@ class DateInputWidget extends \OOUI\TextInputWidget {
                if ( $this->mustBeBefore !== null ) {
                        $config['mustBeBefore'] = $this->mustBeBefore;
                }
-               if ( $this->overlay !== null ) {
-                       $config['overlay'] = $this->overlay;
-               }
+               $config['$overlay'] = true;
                return parent::getConfig( $config );
        }
 
index 5fdc710..c638891 100644 (file)
@@ -60,6 +60,7 @@ class NamespaceInputWidget extends \OOUI\DropdownInputWidget {
                $config['includeAllValue'] = $this->includeAllValue;
                $config['exclude'] = $this->exclude;
                // Skip DropdownInputWidget's getConfig(), we don't need 'options' config
+               $config['dropdown']['$overlay'] = true;
                return \OOUI\InputWidget::getConfig( $config );
        }
 }
index 70b0dcc..e2428ba 100644 (file)
@@ -68,6 +68,7 @@ class SearchInputWidget extends TitleInputWidget {
                if ( $this->dataLocation ) {
                        $config['dataLocation'] = $this->dataLocation;
                }
+               $config['$overlay'] = true;
                return parent::getConfig( $config );
        }
 }
index 3abfbd0..45dd1aa 100644 (file)
@@ -58,6 +58,7 @@ class SelectWithInputWidget extends \OOUI\Widget {
        public function getConfig( &$config ) {
                $config['textinput'] = $this->config['textinput'];
                $config['dropdowninput'] = $this->config['dropdowninput'];
+               $config['dropdowninput']['dropdown']['$overlay'] = true;
                $config['or'] = $this->config['or'];
                return parent::getConfig( $config );
        }
index a29c3dc..15f48e5 100644 (file)
@@ -75,6 +75,7 @@ class TitleInputWidget extends \OOUI\TextInputWidget {
                if ( $this->validateTitle !== null ) {
                        $config['validateTitle'] = $this->validateTitle;
                }
+               $config['$overlay'] = true;
                return parent::getConfig( $config );
        }
 }
index a058ab6..9385b48 100644 (file)
@@ -25,4 +25,9 @@ class UserInputWidget extends \OOUI\TextInputWidget {
        protected function getJavaScriptClassName() {
                return 'mw.widgets.UserInputWidget';
        }
+
+       public function getConfig( &$config ) {
+               $config['$overlay'] = true;
+               return parent::getConfig( $config );
+       }
 }
index ea32058..5c4a91f 100644 (file)
@@ -61,6 +61,7 @@ class UsersMultiselectWidget extends \OOUI\Widget {
                        $config['placeholder'] = $this->inputPlaceholder;
                }
 
+               $config['$overlay'] = true;
                return parent::getConfig( $config );
        }
 
index 9d2e93b..2265132 100644 (file)
@@ -89,7 +89,7 @@
         *     calendar uses relative positioning.
         */
        mw.widgets.DateInputWidget = function MWWDateInputWidget( config ) {
-               var placeholderDateFormat, mustBeAfter, mustBeBefore;
+               var placeholderDateFormat, mustBeAfter, mustBeBefore, $overlay;
 
                // Config initialization
                config = $.extend( {
                        .addClass( 'mw-widget-dateInputWidget' )
                        .append( this.$handle, this.textInput.$element, this.calendar.$element );
 
-               // config.overlay is the selector to be used for config.$overlay, specified from PHP
-               if ( config.overlay ) {
-                       config.$overlay = $( config.overlay );
-               }
+               $overlay = config.$overlay === true ? OO.ui.getDefaultOverlay() : config.$overlay;
 
-               if ( config.$overlay ) {
+               if ( $overlay ) {
                        this.calendar.setFloatableContainer( this.$element );
-                       config.$overlay.append( this.calendar.$element );
+                       $overlay.append( this.calendar.$element );
 
                        // The text input and calendar are not in DOM order, so fix up focus transitions.
                        this.textInput.$input.on( 'keydown', function ( e ) {
index 37c0554..d295ca7 100644 (file)
@@ -64,6 +64,7 @@
                        } );
                } );
                capsulesWidget = new OO.ui.CapsuleMultiselectWidget( {
+                       $overlay: true,
                        menu: {
                                items: capsulesOptions
                        }
index b7d3205..5b12407 100644 (file)
@@ -13,7 +13,7 @@ class ApiClearHasMsgTest extends ApiTestCase {
        public function testClearFlag() {
                $user = self::$users['sysop']->getUser();
                $user->setNewtalk( true );
-               $this->assertTrue( $user->getNewtalk() );
+               $this->assertTrue( $user->getNewtalk(), 'sanity check' );
 
                $data = $this->doApiRequest( [ 'action' => 'clearhasmsg' ], [] );
 
index 155a08e..ea13a0d 100644 (file)
@@ -70,6 +70,9 @@ class ApiComparePagesTest extends ApiTestCase {
                        'page', [ 'page_latest' => 0 ], [ 'page_id' => self::$repl['pageE'] ]
                );
 
+               self::$repl['revF1'] = $this->addPage( 'F', "== Section 1 ==\nF 1.1\n\n== Section 2 ==\nF 1.2" );
+               self::$repl['pageF'] = Title::newFromText( 'ApiComparePagesTest F' )->getArticleId();
+
                WikiPage::factory( Title::newFromText( 'ApiComparePagesTest C' ) )
                        ->doDeleteArticleReal( 'Test for ApiComparePagesTest' );
 
@@ -372,6 +375,26 @@ class ApiComparePagesTest extends ApiTestCase {
                                ],
                                false, true
                        ],
+                       'Basic diff, test with sections' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'fromsection' => 1,
+                                       'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+                                       'tosection' => 2,
+                               ],
+                               [
+                                       'compare' => [
+                                               'body' => '<tr><td colspan="2" class="diff-lineno" id="mw-diff-left-l1" >Line 1:</td>' . "\n"
+                                                       . '<td colspan="2" class="diff-lineno">Line 1:</td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div>== Section <del class="diffchange diffchange-inline">1 </del>==</div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div>== Section <ins class="diffchange diffchange-inline">2 </ins>==</div></td></tr>' . "\n"
+                                                       . '<tr><td class=\'diff-marker\'>−</td><td class=\'diff-deletedline\'><div><del class="diffchange diffchange-inline">F 1.1</del></div></td><td class=\'diff-marker\'>+</td><td class=\'diff-addedline\'><div><ins class="diffchange diffchange-inline">To text?</ins></div></td></tr>' . "\n",
+                                               'fromid' => '{{REPL:pageF}}',
+                                               'fromrevid' => '{{REPL:revF1}}',
+                                               'fromns' => '0',
+                                               'fromtitle' => 'ApiComparePagesTest F',
+                                       ]
+                               ],
+                       ],
                        'Diff with all props' => [
                                [
                                        'fromrev' => '{{REPL:revB1}}',
@@ -568,6 +591,26 @@ class ApiComparePagesTest extends ApiTestCase {
                                [],
                                'compare-no-title',
                        ],
+                       'Error, test with invalid from section ID' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'fromsection' => 5,
+                                       'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+                                       'tosection' => 2,
+                               ],
+                               [],
+                               'nosuchfromsection',
+                       ],
+                       'Error, test with invalid to section ID' => [
+                               [
+                                       'fromtitle' => 'ApiComparePagesTest F',
+                                       'fromsection' => 1,
+                                       'totext' => "== Section 1 ==\nTo text\n\n== Section 2 ==\nTo text?",
+                                       'tosection' => 5,
+                               ],
+                               [],
+                               'nosuchtosection',
+                       ],
                        'Error, Relative diff, no from revision' => [
                                [
                                        'fromtext' => 'Foo',
index 2bafa03..2ba7bdc 100644 (file)
@@ -154,4 +154,21 @@ class CommandTest extends PHPUnit_Framework_TestCase {
                $this->assertSame( 1, count( $logger->getBuffer() ) );
                $this->assertSame( trim( $logger->getBuffer()[0][2]['error'] ), 'ThisIsStderr' );
        }
+
+       public function testInput() {
+               $this->requirePosix();
+
+               $command = new Command();
+               $command->params( 'cat' );
+               $command->input( 'abc' );
+               $result = $command->execute();
+               $this->assertSame( 'abc', $result->getStdout() );
+
+               // now try it with something that does not fit into a single block
+               $command = new Command();
+               $command->params( 'cat' );
+               $command->input( str_repeat( '!', 1000000 ) );
+               $result = $command->execute();
+               $this->assertSame( 1000000, strlen( $result->getStdout() ) );
+       }
 }