];
}
+ // Display image SHA-1 value
+ if ( $title->inNamespace( NS_FILE ) ) {
+ $fileObj = wfFindFile( $title );
+ if ( $fileObj !== false ) {
+ $output = $fileObj->getSha1();
+ $pageInfo['header-basic'][] = [
+ $this->msg( 'pageinfo-file-hash' ),
+ $output
+ ];
+ }
+ }
+
// Page protection
$pageInfo['header-restrictions'] = [];
// 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' ],
];
}
/** @var string */
private $method;
+ /** @var string|null */
+ private $inputString;
+
/** @var bool */
private $doIncludeStderr = false;
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.
}
$desc = [
- 0 => [ 'file', 'php://stdin', 'r' ],
+ 0 => $this->inputString === null ? [ 'file', 'php://stdin', 'r' ] : [ 'pipe', 'r' ],
1 => [ 'pipe', 'w' ],
2 => [ 'pipe', 'w' ],
];
$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;
}
}
- $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
@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 ) {
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 );
}
$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] );
}
}
*
* Use call chaining with this class for expressiveness:
* $result = Shell::command( 'some command' )
+ * ->input( 'foo' )
* ->environment( [ 'ENVIRONMENT_VARIABLE' => 'VALUE' ] )
* ->limits( [ 'time' => 300 ] )
* ->execute();
)
)
);
+ $config['namespace']['dropdown']['$overlay'] = true;
return parent::getConfig( $config );
}
}
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 );
}
}
protected $precision = null;
protected $mustBeAfter = null;
protected $mustBeBefore = null;
- protected $overlay = null;
/**
* @param array $config Configuration options
* 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( [
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 ) {
if ( $this->mustBeBefore !== null ) {
$config['mustBeBefore'] = $this->mustBeBefore;
}
- if ( $this->overlay !== null ) {
- $config['overlay'] = $this->overlay;
- }
+ $config['$overlay'] = true;
return parent::getConfig( $config );
}
$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 );
}
}
if ( $this->dataLocation ) {
$config['dataLocation'] = $this->dataLocation;
}
+ $config['$overlay'] = true;
return parent::getConfig( $config );
}
}
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 );
}
if ( $this->validateTitle !== null ) {
$config['validateTitle'] = $this->validateTitle;
}
+ $config['$overlay'] = true;
return parent::getConfig( $config );
}
}
protected function getJavaScriptClassName() {
return 'mw.widgets.UserInputWidget';
}
+
+ public function getConfig( &$config ) {
+ $config['$overlay'] = true;
+ return parent::getConfig( $config );
+ }
}
$config['placeholder'] = $this->inputPlaceholder;
}
+ $config['$overlay'] = true;
return parent::getConfig( $config );
}
"pageinfo-category-subcats": "Number of subcategories",
"pageinfo-category-files": "Number of files",
"pageinfo-user-id": "User ID",
+ "pageinfo-file-hash": "Hash value",
"markaspatrolleddiff": "Mark as patrolled",
"markaspatrolledlink": "[$1]",
"markaspatrolledtext": "Mark this page as patrolled",
"pageinfo-category-subcats": "See also:\n* {{msg-mw|Pageinfo-category-pages}}\n* {{msg-mw|Pageinfo-category-files}}",
"pageinfo-category-files": "See also:\n* {{msg-mw|Pageinfo-category-pages}}\n* {{msg-mw|Pageinfo-category-subcats}}",
"pageinfo-user-id": "The numeric ID for a user\n{{Identical|User ID}}",
+ "pageinfo-file-hash": "Base-36 SHA-1 value of the file",
"markaspatrolleddiff": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolledtext}}\n{{Identical|Mark as patrolled}}",
"markaspatrolledlink": "{{notranslate}}\nParameters:\n* $1 - link which has text {{msg-mw|Markaspatrolledtext}}",
"markaspatrolledtext": "{{doc-actionlink}}\nSee also:\n* {{msg-mw|Markaspatrolleddiff}}",
* 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 ) {
} );
} );
capsulesWidget = new OO.ui.CapsuleMultiselectWidget( {
+ $overlay: true,
menu: {
items: capsulesOptions
}
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' ], [] );
$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() ) );
+ }
}