<exclude name="MediaWiki.Commenting.MissingCovers.MissingCovers" />
<exclude name="MediaWiki.NamingConventions.LowerCamelFunctionsName.FunctionName" />
<exclude name="MediaWiki.Usage.DbrQueryUsage.DbrQueryFound" />
- <exclude name="MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage" />
<exclude name="MediaWiki.Usage.ForbiddenFunctions.passthru" />
<exclude name="MediaWiki.VariableAnalysis.ForbiddenGlobalVariables.ForbiddenGlobal$wgTitle" />
<exclude name="MediaWiki.WhiteSpace.SpaceBeforeSingleLineComment.NewLineComment" />
"apihelp-compare-param-toslots": "Override content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that are to be modified. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, and <var>tocontentformat-{slot}</var> to specify content for each slot.",
"apihelp-compare-param-totext-{slot}": "Text of the specified slot. If omitted, the slot is removed from the revision.",
"apihelp-compare-param-tosection-{slot}": "When <var>totext-{slot}</var> is the content of a single section, this is the section number. It will be merged into the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var> as if for a section edit.",
- "apihelp-compare-param-toslots": "Specify content to use instead of the content of the revision specified by <var>totitle</var>, <var>toid</var> or <var>torev</var>.\n\nThis parameter specifies the slots that have content. Use <var>totext-{slot}</var>, <var>tocontentmodel-{slot}</var>, and <var>tocontentformat-{slot}</var> to specify content for each slot.",
- "apihelp-compare-param-totext-{slot}": "Text of the specified slot.",
"apihelp-compare-param-tocontentmodel-{slot}": "Content model of <var>totext-{slot}</var>. If not supplied, it will be guessed based on the other parameters.",
"apihelp-compare-param-tocontentformat-{slot}": "Content serialization format of <var>totext-{slot}</var>.",
"apihelp-compare-param-totext": "Specify <kbd>toslots=main</kbd> and use <var>totext-main</var> instead.",
public function msg( $key ) {
$args = func_get_args();
+ // phpcs:ignore MediaWiki.Usage.ExtendClassUsage.FunctionVarUsage
return wfMessage( ...$args )->setContext( $this );
}
}
*/
const DIFF_VERSION = '1.12';
- /** @var int Revision ID or 0 for current */
+ /**
+ * Revision ID for the old revision. 0 for the revision previous to $mNewid, false
+ * if the diff does not have an old revision (e.g. 'oldid=<first revision of page>&diff=prev'),
+ * or the revision does not exist, null if the revision is unsaved.
+ * @var int|false|null
+ */
protected $mOldid;
- /** @var int|string Revision ID or null for current or an alias such as 'next' */
+ /**
+ * Revision ID for the new revision. 0 for the last revision of the current page
+ * (as defined by the request context), false if the revision does not exist, null
+ * if it is unsaved, or an alias such as 'next'.
+ * @var int|string|false|null
+ */
protected $mNewid;
- private $mOldTags;
- private $mNewTags;
-
/**
* Old revision (left pane).
* Allowed to be an unsaved revision, unlikely that's ever needed though.
- * Null when the old revision does not exist; this can happen when using
- * diff=prev on the first revision.
+ * False when the old revision does not exist; this can happen when using
+ * diff=prev on the first revision. Null when the revision should exist but
+ * doesn't (e.g. load failure); loadRevisionData() will return false in that
+ * case. Also null until lazy-loaded. Ignored completely when isContentOverridden
+ * is set.
* Since 1.32 public access is deprecated.
- * @var Revision|null
+ * @var Revision|null|false
*/
protected $mOldRev;
/**
* New revision (right pane).
* Note that this might be an unsaved revision (e.g. for edit preview).
- * Null only in case of load failure; diff methods will just return an error message in that case.
+ * Null in case of load failure; diff methods will just return an error message in that case,
+ * and loadRevisionData() will return false. Also null until lazy-loaded. Ignored completely
+ * when isContentOverridden is set.
* Since 1.32 public access is deprecated.
* @var Revision|null
*/
*/
protected $mNewPage;
+ /**
+ * Change tags of $mOldRev or null if it does not exist / is not saved.
+ * @var string[]|null
+ */
+ private $mOldTags;
+
+ /**
+ * Change tags of $mNewRev or null if it does not exist / is not saved.
+ * @var string[]|null
+ */
+ private $mNewTags;
+
/**
* @var Content|null
* @deprecated since 1.32, content slots are now handled by the corresponding SlotDiffRenderer.
/**
* Get the old and new content objects for all slots.
* This method does not do any permission checks.
- * @return array [ role => [ 'old' => SlotRecord, 'new' => SlotRecord ], ... ]
+ * @return array [ role => [ 'old' => SlotRecord|null, 'new' => SlotRecord|null ], ... ]
*/
protected function getSlotContents() {
if ( $this->isContentOverridden ) {
'new' => $this->mNewContent,
]
];
+ } elseif ( !$this->loadRevisionData() ) {
+ return [];
}
- $oldRev = $this->mOldRev->getRevisionRecord();
- $newRev = $this->mNewRev->getRevisionRecord();
+ $newSlots = $this->mNewRev->getRevisionRecord()->getSlots()->getSlots();
+ if ( $this->mOldRev ) {
+ $oldSlots = $this->mOldRev->getRevisionRecord()->getSlots()->getSlots();
+ } else {
+ $oldSlots = [];
+ }
// The order here will determine the visual order of the diff. The current logic is
- // changed first, then added, then deleted. This is ad hoc and should not be relied on
- // - in the future we may want the ordering to depend on the page type.
- $roles = array_merge( $newRev->getSlotRoles(), $oldRev->getSlotRoles() );
- $oldSlots = $oldRev->getSlots()->getSlots();
- $newSlots = $newRev->getSlots()->getSlots();
+ // slots of the new revision first in natural order, then deleted ones. This is ad hoc
+ // and should not be relied on - in the future we may want the ordering to depend
+ // on the page type.
+ $roles = array_merge( array_keys( $newSlots ), array_keys( $oldSlots ) );
$slots = [];
foreach ( $roles as $role ) {
}
/**
- * @return int
+ * Get the ID of old revision (left pane) of the diff. 0 for the revision
+ * previous to getNewid(), false if the old revision does not exist, null
+ * if it's unsaved.
+ * To get a real revision ID instead of 0, call loadRevisionData() first.
+ * @return int|false|null
*/
public function getOldid() {
$this->loadRevisionIds();
}
/**
- * @return bool|int
+ * Get the ID of new revision (right pane) of the diff. 0 for the current revision,
+ * false if the new revision does not exist, null if it's unsaved.
+ * To get a real revision ID instead of 0, call loadRevisionData() first.
+ * @return int|false|null
*/
public function getNewid() {
$this->loadRevisionIds();
$this->mOldContent = $oldRevision ? $oldRevision->getContent( 'main',
RevisionRecord::FOR_THIS_USER, $this->getUser() ) : null;
} else {
- $this->mOldRev = $this->mOldid = $this->mOldPage = null;
+ $this->mOldPage = null;
+ $this->mOldRev = $this->mOldid = false;
}
$this->mNewRev = new Revision( $newRevision );
$this->mNewid = $newRevision->getId();
* @param int $old Revision id, e.g. from URL parameter 'oldid'
* @param int|string $new Revision id or strings 'next' or 'prev', e.g. from URL parameter 'diff'
*
- * @return int[] List of two revision ids, older first, later second.
+ * @return array List of two revision ids, older first, later second.
* Zero signifies invalid argument passed.
* false signifies that there is no previous/next revision ($old is the oldest/newest one).
*/
}
/**
- * Load revision metadata for the specified articles. If newid is 0, then compare
- * the old article in oldid to the current article; if oldid is 0, then
- * compare the current article to the immediately previous one (ignoring the
- * value of newid).
+ * Load revision metadata for the specified revisions. If newid is 0, then compare
+ * the old revision in oldid to the current revision of the current page (as defined
+ * by the request context); if oldid is 0, then compare the revision in newid to the
+ * immediately previous one.
*
* If oldid is false, leave the corresponding revision object set
- * to false. This is impossible via ordinary user input, and is provided for
- * API convenience.
+ * to false. This can happen with 'diff=prev' pointing to a non-existent revision,
+ * and is also used directly by the API.
*
- * @return bool Whether both revisions were loaded successfully.
+ * @return bool Whether both revisions were loaded successfully. Setting mOldRev
+ * to false counts as successful loading.
*/
public function loadRevisionData() {
if ( $this->mRevisionsLoaded ) {
- return $this->isContentOverridden || $this->mNewRev && $this->mOldRev;
+ return $this->isContentOverridden || $this->mNewRev && !is_null( $this->mOldRev );
}
// Whether it succeeds or fails, we don't want to try again
/**
* Load the text of the revisions, as well as revision data.
+ * When the old revision is missing (mOldRev is false), loading mOldContent is not attempted.
*
* @return bool Whether the content of both revisions could be loaded successfully.
+ * (When mOldRev is false, that still counts as a success.)
+ *
*/
public function loadText() {
if ( $this->mTextLoaded == 2 ) {
- return $this->loadRevisionData() && $this->mOldContent && $this->mNewContent;
+ return $this->loadRevisionData() && ( $this->mOldRev === false || $this->mOldContent )
+ && $this->mNewContent;
}
// Whether it succeeds or fails, we don't want to try again
}
}
- if ( $this->mNewRev ) {
- $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
- Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
- if ( $this->mNewContent === null ) {
- return false;
- }
+ $this->mNewContent = $this->mNewRev->getContent( Revision::FOR_THIS_USER, $this->getUser() );
+ Hooks::run( 'DifferenceEngineLoadTextAfterNewContentIsLoaded', [ $this ] );
+ if ( $this->mNewContent === null ) {
+ return false;
}
return true;
}
if ( in_array( $type, [ 'asc', 'desc' ] ) ) {
- $attrs['title'] = wfMessage( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text();
+ $attrs['title'] = $this->msg( $type == 'asc' ? 'sort-ascending' : 'sort-descending' )->text();
}
if ( $type ) {
$t = $embed . implode( "{$pop}{$embed}", $allCats['normal'] ) . $pop;
$msg = $this->msg( 'pagecategories' )->numParams( count( $allCats['normal'] ) )->escaped();
- $linkPage = wfMessage( 'pagecategorieslink' )->inContentLanguage()->text();
+ $linkPage = $this->msg( 'pagecategorieslink' )->inContentLanguage()->text();
$title = Title::newFromText( $linkPage );
$link = $title ? Linker::link( $title, $msg ) : $msg;
$s .= '<div id="mw-normal-catlinks" class="mw-normal-catlinks">' .
* @param string $message
*/
public function addToSidebar( &$bar, $message ) {
- $this->addToSidebarPlain( $bar, wfMessage( $message )->inContentLanguage()->plain() );
+ $this->addToSidebarPlain( $bar, $this->msg( $message )->inContentLanguage()->plain() );
}
/**
$attribs = [];
if ( !is_null( $tooltip ) ) {
- $attribs['title'] = wfMessage( 'editsectionhint' )->rawParams( $tooltip )
+ $attribs['title'] = $this->msg( 'editsectionhint' )->rawParams( $tooltip )
->inLanguage( $lang )->text();
}
$links = [
'editsection' => [
- 'text' => wfMessage( 'editsection' )->inLanguage( $lang )->escaped(),
+ 'text' => $this->msg( 'editsection' )->inLanguage( $lang )->escaped(),
'targetTitle' => $nt,
'attribs' => $attribs,
'query' => [ 'action' => 'edit', 'section' => $section ],
$result .= implode(
'<span class="mw-editsection-divider">'
- . wfMessage( 'pipe-separator' )->inLanguage( $lang )->escaped()
+ . $this->msg( 'pipe-separator' )->inLanguage( $lang )->escaped()
. '</span>',
$linksHtml
);
$form->addPreText(
Html::openElement( 'dl' )
- . Html::element( 'dt', [], wfMessage( 'credentialsform-provider' )->text() )
+ . Html::element( 'dt', [], $this->msg( 'credentialsform-provider' )->text() )
. Html::element( 'dd', [], $info['provider'] )
- . Html::element( 'dt', [], wfMessage( 'credentialsform-account' )->text() )
+ . Html::element( 'dt', [], $this->msg( 'credentialsform-account' )->text() )
. Html::element( 'dd', [], $info['account'] )
. Html::closeElement( 'dl' )
);
$dateRangeSelection = Html::rawElement(
'div',
[],
- Xml::label( wfMessage( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
+ Xml::label( $this->msg( 'date-range-from' )->text(), 'mw-date-start' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-start',
'value' => $this->opts['start'],
'longDisplayFormat' => true,
] ) . '<br>' .
- Xml::label( wfMessage( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
+ Xml::label( $this->msg( 'date-range-to' )->text(), 'mw-date-end' ) . ' ' .
new DateInputWidget( [
'infusable' => true,
'id' => 'mw-date-end',
* SPF and bounce problems with some mailers (see below).
*/
$mailFrom = new MailAddress( $config->get( 'PasswordSender' ),
- wfMessage( 'emailsender' )->inContentLanguage()->text() );
+ $context->msg( 'emailsender' )->inContentLanguage()->text() );
$replyTo = $from;
} else {
/**
if ( $config->get( 'UserEmailUseReplyTo' ) ) {
$mailFrom = new MailAddress(
$config->get( 'PasswordSender' ),
- wfMessage( 'emailsender' )->inContentLanguage()->text()
+ $context->msg( 'emailsender' )->inContentLanguage()->text()
);
$replyTo = $ccFrom;
} else {
if ( !$this->isActionAllowed( $this->authAction ) ) {
if ( $this->authAction === AuthManager::ACTION_LINK ) {
// looks like no linking provider is installed or willing to take this user
- $titleMessage = wfMessage( 'cannotlink-no-provider-title' );
- $errorMessage = wfMessage( 'cannotlink-no-provider' );
+ $titleMessage = $this->msg( 'cannotlink-no-provider-title' );
+ $errorMessage = $this->msg( 'cannotlink-no-provider' );
throw new ErrorPageError( $titleMessage, $errorMessage );
} else {
// user probably back-button-navigated into an auth session that no longer exists
}
$status = StatusValue::newGood();
- $status->warning( wfMessage( 'unlinkaccounts-success' ) );
+ $status->warning( $this->msg( 'unlinkaccounts-success' ) );
$this->loadAuth( $subPage, null, true ); // update requests so the unlinked one doesn't show up
// Reset sessions - if the user unlinked an account because it was compromised,
$this->mForReUpload = $request->getBool( 'wpForReUpload' ); // updating a file
$commentDefault = '';
- $commentMsg = wfMessage( 'upload-default-description' )->inContentLanguage();
+ $commentMsg = $this->msg( 'upload-default-description' )->inContentLanguage();
if ( !$this->mForReUpload && !$commentMsg->isDisabled() ) {
$commentDefault = $commentMsg->plain();
}
} elseif ( $warning == 'no-change' ) {
$file = $args;
$filename = $file->getTitle()->getPrefixedText();
- $msg = "\t<li>" . wfMessage( 'fileexists-no-change', $filename )->parse() . "</li>\n";
+ $msg = "\t<li>" . $this->msg( 'fileexists-no-change', $filename )->parse() . "</li>\n";
} elseif ( $warning == 'duplicate-version' ) {
$file = $args[0];
$count = count( $args );
$filename = $file->getTitle()->getPrefixedText();
- $message = wfMessage( 'fileexists-duplicate-version' )
+ $message = $this->msg( 'fileexists-duplicate-version' )
->params( $filename )
->numParams( $count );
$msg = "\t<li>" . $message->parse() . "</li>\n";
$ltitle = SpecialPage::getTitleFor( 'Log' );
$llink = $linkRenderer->makeKnownLink(
$ltitle,
- wfMessage( 'deletionlog' )->text(),
+ $this->msg( 'deletionlog' )->text(),
[],
[
'type' => 'delete',
'page' => Title::makeTitle( NS_FILE, $args )->getPrefixedText(),
]
);
- $msg = "\t<li>" . wfMessage( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
+ $msg = "\t<li>" . $this->msg( 'filewasdeleted' )->rawParams( $llink )->parse() . "</li>\n";
} elseif ( $warning == 'duplicate' ) {
$msg = $this->getDupeWarning( $args );
} elseif ( $warning == 'duplicate-archive' ) {
if ( $type !== 'file' && $type !== 'thumb' ) {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-unknown-type', $type )
+ $this->msg( 'uploadstash-bad-path-unknown-type', $type )
);
}
$fileName = strtok( '/' );
$srcNamePos = strrpos( $thumbPart, $fileName );
if ( $srcNamePos === false || $srcNamePos < 1 ) {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-unrecognized-thumb-name' )
+ $this->msg( 'uploadstash-bad-path-unrecognized-thumb-name' )
);
}
$paramString = substr( $thumbPart, 0, $srcNamePos - 1 );
return [ 'file' => $file, 'type' => $type, 'params' => $params ];
} else {
throw new UploadStashBadPathException(
- wfMessage( 'uploadstash-bad-path-no-handler', $file->getMimeType(), $file->getPath() )
+ $this->msg( 'uploadstash-bad-path-no-handler', $file->getMimeType(), $file->getPath() )
);
}
}
$thumbnailImage = $file->transform( $params, $flags );
if ( !$thumbnailImage ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-thumb' )
+ $this->msg( 'uploadstash-file-not-found-no-thumb' )
);
}
// we should have just generated it locally
if ( !$thumbnailImage->getStoragePath() ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-local-path' )
+ $this->msg( 'uploadstash-file-not-found-no-local-path' )
);
}
$this->stash->repo, $thumbnailImage->getStoragePath(), false );
if ( !$thumbFile ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-no-object' )
+ $this->msg( 'uploadstash-file-not-found-no-object' )
);
}
if ( !$status->isOK() ) {
$errors = $status->getErrorsArray();
throw new UploadStashFileNotFoundException(
- wfMessage(
+ $this->msg(
'uploadstash-file-not-found-no-remote-thumb',
print_r( $errors, 1 ),
$scalerThumbUrl
$contentType = $req->getResponseHeader( "content-type" );
if ( !$contentType ) {
throw new UploadStashFileNotFoundException(
- wfMessage( 'uploadstash-file-not-found-missing-content-type' )
+ $this->msg( 'uploadstash-file-not-found-missing-content-type' )
);
}
private function outputLocalFile( File $file ) {
if ( $file->getSize() > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException(
- wfMessage( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
+ $this->msg( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
);
}
$size = strlen( $content );
if ( $size > self::MAX_SERVE_BYTES ) {
throw new SpecialUploadStashTooLargeException(
- wfMessage( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
+ $this->msg( 'uploadstash-file-too-large', self::MAX_SERVE_BYTES )
);
}
// Cancel output buffering and gzipping if set
if ( $thumb ) {
return $thumb->toHtml( [ 'desc-link' => true ] );
} else {
- return wfMessage( 'thumbnail_error', '' )->escaped();
+ return $this->msg( 'thumbnail_error', '' )->escaped();
}
} else {
return htmlspecialchars( $value );
public function testLoadRevisionData() {
$cases = $this->getLoadRevisionDataCases();
- foreach ( $cases as $case ) {
- list( $expectedOld, $expectedNew, $old, $new, $message ) = $case;
+ foreach ( $cases as $testName => $case ) {
+ list( $expectedOld, $expectedNew, $expectedRet, $old, $new ) = $case;
$diffEngine = new DifferenceEngine( $this->context, $old, $new, 2, true, false );
- $diffEngine->loadRevisionData();
+ $ret = $diffEngine->loadRevisionData();
+ $ret2 = $diffEngine->loadRevisionData();
- $this->assertEquals( $diffEngine->getOldid(), $expectedOld, $message );
- $this->assertEquals( $diffEngine->getNewid(), $expectedNew, $message );
+ $this->assertEquals( $expectedOld, $diffEngine->getOldid(), $testName );
+ $this->assertEquals( $expectedNew, $diffEngine->getNewid(), $testName );
+ $this->assertEquals( $expectedRet, $ret, $testName );
+ $this->assertEquals( $expectedRet, $ret2, $testName );
}
}
$revs = self::$revisions;
return [
- [ $revs[2], $revs[3], $revs[3], 'prev', 'diff=prev' ],
- [ $revs[2], $revs[3], $revs[2], 'next', 'diff=next' ],
- [ $revs[1], $revs[3], $revs[1], $revs[3], 'diff=' . $revs[3] ],
- [ $revs[1], $revs[3], $revs[1], 0, 'diff=0' ]
+ 'diff=prev' => [ $revs[2], $revs[3], true, $revs[3], 'prev' ],
+ 'diff=next' => [ $revs[2], $revs[3], true, $revs[2], 'next' ],
+ 'diff=' . $revs[3] => [ $revs[1], $revs[3], true, $revs[1], $revs[3] ],
+ 'diff=0' => [ $revs[1], $revs[3], true, $revs[1], 0 ],
+ 'diff=prev&oldid=<first>' => [ false, $revs[0], true, $revs[0], 'prev' ],
+ 'invalid' => [ 123456789, $revs[1], false, 123456789, $revs[1] ],
];
}