if ( $longContext ) {
$s = wfMessage( $longContext, $s );
} elseif ( $shortContext ) {
- $wrapper = new RawMessage( "\n\$1\n", $s );
+ $wrapper = new RawMessage( "\n\$1\n", [ $s ] );
$wrapper->parse();
$s = wfMessage( $shortContext, $wrapper );
}
* - describe (since 1.21)
* - null
*
+ * FSFile/TempFSFile object support was added in 1.27.
+ *
* a) Create a new file in storage with the contents of a string
* @code
* array(
* @code
* array(
* 'op' => 'store',
- * 'src' => <file system path>,
+ * 'src' => <file system path, FSFile, or TempFSFile>,
* 'dst' => <storage path>,
* 'overwrite' => <boolean>,
* 'overwriteSame' => <boolean>,
if ( !count( $ops ) ) {
return Status::newGood(); // nothing to do
}
+
+ $ops = $this->resolveFSFileObjects( $ops );
if ( empty( $opts['force'] ) ) { // sanity
unset( $opts['nonLocking'] );
}
+
/** @noinspection PhpUnusedLocalVariableInspection */
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+
return $this->doOperationsInternal( $ops, $opts );
}
* - describe (since 1.21)
* - null
*
+ * FSFile/TempFSFile object support was added in 1.27.
+ *
* a) Create a new file in storage with the contents of a string
* @code
* array(
* @code
* array(
* 'op' => 'store',
- * 'src' => <file system path>,
+ * 'src' => <file system path, FSFile, or TempFSFile>,
* 'dst' => <storage path>,
* 'headers' => <HTTP header name/value map> # since 1.21
* )
if ( !count( $ops ) ) {
return Status::newGood(); // nothing to do
}
+
+ $ops = $this->resolveFSFileObjects( $ops );
foreach ( $ops as &$op ) {
$op['overwrite'] = true; // avoids RTTs in key/value stores
}
+
/** @noinspection PhpUnusedLocalVariableInspection */
$scope = $this->getScopedPHPBehaviorForOps(); // try to ignore client aborts
+
return $this->doQuickOperationsInternal( $ops );
}
return $this->fileJournal;
}
+ /**
+ * Convert FSFile 'src' paths to string paths (with an 'srcRef' field set to the FSFile)
+ *
+ * The 'srcRef' field keeps any TempFSFile objects in scope for the backend to have it
+ * around as long it needs (which may vary greatly depending on configuration)
+ *
+ * @param array $ops File operation batch for FileBaclend::doOperations()
+ * @return array File operation batch
+ */
+ protected function resolveFSFileObjects( array $ops ) {
+ foreach ( $ops as &$op ) {
+ $src = isset( $op['src'] ) ? $op['src'] : null;
+ if ( $src instanceof FSFile ) {
+ $op['srcRef'] = $src;
+ $op['src'] = $src->getPath();
+ }
+ }
+ unset( $op );
+
+ return $ops;
+ }
+
/**
* Check if a given path is a "mwstore://" path.
* This does not do any further validation or any existence checks.
}
$realOps = $this->substOpBatchPaths( $ops, $backend );
- if ( $this->asyncWrites && !$this->hasStoreOperation( $ops ) ) {
+ if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
// Bind $scopeLock to the callback to preserve locks
DeferredUpdates::addCallableUpdate(
function() use ( $backend, $realOps, $opts, $scopeLock ) {
}
/**
- * @param array $ops File operation batch map
- * @return bool
+ * @param array $ops File operations for FileBackend::doOperations()
+ * @return bool Whether there are file path sources with outside lifetime/ownership
*/
- protected function hasStoreOperation( array $ops ) {
+ protected function hasVolatileSources( array $ops ) {
foreach ( $ops as $op ) {
- if ( $op['op'] === 'store' ) {
- return true;
+ if ( $op['op'] === 'store' && !isset( $op['srcRef'] ) ) {
+ return true; // source file might be deleted anytime after do*Operations()
}
}
}
$realOps = $this->substOpBatchPaths( $ops, $backend );
- if ( $this->asyncWrites && !$this->hasStoreOperation( $ops ) ) {
+ if ( $this->asyncWrites && !$this->hasVolatileSources( $ops ) ) {
DeferredUpdates::addCallableUpdate(
function() use ( $backend, $realOps ) {
$backend->doQuickOperations( $realOps );
* This function can be used to write to otherwise read-only foreign repos.
* This is intended for copying generated thumbnails into the repo.
*
- * @param string $src Source file system path, storage path, or virtual URL
+ * @param string|FSFile $src Source file system path, storage path, or virtual URL
* @param string $dst Virtual URL or storage path
* @param array|string|null $options An array consisting of a key named headers
* listing extra headers. If a string, taken as content-disposition header.
* All path parameters may be a file system path, storage path, or virtual URL.
* When "headers" are given they are used as HTTP headers if supported.
*
- * @param array $triples List of (source path, destination path, disposition)
+ * @param array $triples List of (source path or FSFile, destination path, disposition)
* @return FileRepoStatus
*/
public function quickImportBatch( array $triples ) {
$operations = [];
foreach ( $triples as $triple ) {
list( $src, $dst ) = $triple;
- $src = $this->resolveToStoragePath( $src );
+ if ( $src instanceof FSFile ) {
+ $op = 'store';
+ } else {
+ $src = $this->resolveToStoragePath( $src );
+ $op = FileBackend::isStoragePath( $src ) ? 'copy' : 'store';
+ }
$dst = $this->resolveToStoragePath( $dst );
if ( !isset( $triple[2] ) ) {
}
$operations[] = [
- 'op' => FileBackend::isStoragePath( $src ) ? 'copy' : 'store',
+ 'op' => $op,
'src' => $src,
'dst' => $dst,
'headers' => $headers
/**
* Perform a full text search query and return a result set.
- * If title searches are not supported or disabled, return null.
+ * If full text searches are not supported or disabled, return null.
* STUB
*
* @param string $term Raw search term
// Button styling
// ----------------------------------------------------------------------------
-.button-colors(@bgColor, @highlightColor, @activeColor) {
+.button-colors( @bgColor, @highlightColor, @activeColor ) {
background: @bgColor;
&:hover {
}
}
-.button-colors(@bgColor, @highlightColor, @activeColor) when (lightness(@bgColor) >= 70%) {
+.button-colors( @bgColor, @highlightColor, @activeColor ) when ( lightness( @bgColor ) >= 70% ) {
color: @colorButtonText;
border: 1px solid @colorGray12;
}
}
-.button-colors(@bgColor, @highlightColor, @activeColor) when (lightness(@bgColor) < 70%) {
+.button-colors( @bgColor, @highlightColor, @activeColor ) when ( lightness( @bgColor ) < 70% ) {
color: #fff;
// border of the same color as background so that light background and
// dark background buttons are the same height and width
}
}
-.button-colors-quiet(@textColor, @highlightColor, @activeColor) {
+.button-colors-quiet( @textColor, @highlightColor, @activeColor ) {
// Quiet buttons all start gray, and reveal
// constructive/progressive/destructive color on hover and active.
color: @colorButtonText;
}
}
- // Constructive buttons
+ // Constructive buttons (deprecated, consolidated with `progressive` – see T110555)
//
// Use constructive buttons for actions which result in a final action in the process that results
// in a change of state.
//
// Styleguide 2.1.2.
&.mw-ui-constructive {
- .button-colors( @colorConstructive, @colorConstructiveHighlight, @colorConstructiveActive );
+ .button-colors( @colorProgressive, @colorProgressiveHighlight, @colorProgressiveActive );
&.mw-ui-quiet {
- .button-colors-quiet( @colorConstructive, @colorConstructiveHighlight, @colorConstructiveActive );
+ .button-colors-quiet( @colorProgressive, @colorProgressiveHighlight, @colorProgressiveActive );
}
}
/**
* @dataProvider provideGetMessage
* @covers Status::getMessage
- * @todo test long and short context messages generated through this method
*/
- public function testGetMessage( Status $status, $expectedParams = [], $expectedKey ) {
+ public function testGetMessage(
+ Status $status, $expectedParams = [], $expectedKey, $expectedWrapper
+ ) {
$message = $status->getMessage();
$this->assertInstanceOf( 'Message', $message );
$this->assertEquals( $expectedParams, $message->getParams(), 'Message::getParams' );
$this->assertEquals( $expectedKey, $message->getKey(), 'Message::getKey' );
+
+ $message = $status->getMessage( 'wrapper-short', 'wrapper-long' );
+ $this->assertInstanceOf( 'Message', $message );
+ $this->assertEquals( $expectedWrapper, $message->getKey(), 'Message::getKey with wrappers' );
+ $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
+
+ $message = $status->getMessage( 'wrapper' );
+ $this->assertInstanceOf( 'Message', $message );
+ $this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
+ $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
+
+ $message = $status->getMessage( false, 'wrapper' );
+ $this->assertInstanceOf( 'Message', $message );
+ $this->assertEquals( 'wrapper', $message->getKey(), 'Message::getKey with wrappers' );
+ $this->assertCount( 1, $message->getParams(), 'Message::getParams with wrappers' );
}
/**
$testCases['GoodStatus'] = [
new Status(),
[ "Status::getMessage called for a good result, this is incorrect\n" ],
- 'internalerror_info'
+ 'internalerror_info',
+ 'wrapper-short'
];
$status = new Status();
$testCases['GoodButNoError'] = [
$status,
[ "Status::getMessage: Invalid result object: no error text but not OK\n" ],
- 'internalerror_info'
+ 'internalerror_info',
+ 'wrapper-short'
];
$status = new Status();
$testCases['1StringWarning'] = [
$status,
[],
- 'fooBar!'
+ 'fooBar!',
+ 'wrapper-short'
];
// FIXME: Assertion tries to compare a StubUserLang with a Language object, because
// $testCases[ '2StringWarnings' ] = array(
// $status,
// array( new Message( 'fooBar!' ), new Message( 'fooBar2!' ) ),
-// "* \$1\n* \$2"
+// "* \$1\n* \$2",
+// 'wrapper-long'
// );
$status = new Status();
$testCases['1MessageWarning'] = [
$status,
[ 'foo', 'bar' ],
- 'fooBar!'
+ 'fooBar!',
+ 'wrapper-short'
];
$status = new Status();
$testCases['2MessageWarnings'] = [
$status,
[ new Message( 'fooBar!', [ 'foo', 'bar' ] ), new Message( 'fooBar2!' ) ],
- "* \$1\n* \$2"
+ "* \$1\n* \$2",
+ 'wrapper-long'
];
return $testCases;