$comment = CommentStoreComment::newUnsavedComment( $comment, $data );
# Truncate comment in a Unicode-sensitive manner
- $comment->text = $this->lang->truncate( $comment->text, self::MAX_COMMENT_LENGTH );
- if ( mb_strlen( $comment->text, 'UTF-8' ) > self::COMMENT_CHARACTER_LIMIT ) {
- $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this->lang )->escaped();
- if ( mb_strlen( $ellipsis ) >= self::COMMENT_CHARACTER_LIMIT ) {
- // WTF?
- $ellipsis = '...';
- }
- $maxLength = self::COMMENT_CHARACTER_LIMIT - mb_strlen( $ellipsis, 'UTF-8' );
- $comment->text = mb_substr( $comment->text, 0, $maxLength, 'UTF-8' ) . $ellipsis;
- }
+ $comment->text = $this->lang->truncateForVisual( $comment->text, self::COMMENT_CHARACTER_LIMIT );
if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
$dbData = $comment->data;
$comment = $this->createComment( $dbw, $comment, $data );
if ( $this->stage <= MIGRATION_WRITE_BOTH ) {
- $fields[$key] = $this->lang->truncate( $comment->text, 255 );
+ $fields[$key] = $this->lang->truncateForDatabase( $comment->text, 255 );
}
if ( $this->stage >= MIGRATION_WRITE_BOTH ) {
$text = $this->getNativeData();
- $truncatedtext = $wgContLang->truncate(
+ $truncatedtext = $wgContLang->truncateForDatabase(
preg_replace( "/[\n\r]/", ' ', $text ),
max( 0, $maxlength ) );
$nt,
htmlspecialchars(
is_int( $this->getCaptionLength() ) ?
- $lang->truncate( $nt->getText(), $this->getCaptionLength() ) :
+ $lang->truncateForVisual( $nt->getText(), $this->getCaptionLength() ) :
$nt->getText()
),
[
}
/**
- * Length to truncate filename to in caption when using "showfilename" (if int).
+ * Length (in characters) to truncate filename to in caption when using "showfilename" (if int).
* A value of 'true' will truncate the filename to one line using CSS, while
* 'false' will disable truncating.
*
use MediaWiki\Widget\UsersMultiselectWidget;
/**
- * Implements a capsule multiselect input field for user names.
+ * Implements a tag multiselect input field for user names.
*
* Besides the parameters recognized by HTMLUserTextField, additional recognized
* parameters are:
* to perform updates, if the edit was already saved.
* @param RevisionSlotsUpdate|null $forUpdate The new content to be saved by the edit (pre PST),
* if the edit was not yet saved.
+ * @param bool $forEdit Only re-use if the cached DerivedPageDataUpdater has the current
+ * revision as the edit's parent revision. This ensures that the same
+ * DerivedPageDataUpdater cannot be re-used for two consecutive edits.
*
* @return DerivedPageDataUpdater
*/
private function getDerivedDataUpdater(
User $forUser = null,
RevisionRecord $forRevision = null,
- RevisionSlotsUpdate $forUpdate = null
+ RevisionSlotsUpdate $forUpdate = null,
+ $forEdit = false
) {
if ( !$forRevision && !$forUpdate ) {
// NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
&& !$this->derivedDataUpdater->isReusableFor(
$forUser,
$forRevision,
- $forUpdate
+ $forUpdate,
+ $forEdit ? $this->getLatest() : null
)
) {
$this->derivedDataUpdater = null;
* @since 1.32
*
* @param User $user
+ * @param RevisionSlotsUpdate|null $forUpdate If given, allows any cached ParserOutput
+ * that may already have been returned via getDerivedDataUpdater to be re-used.
*
* @return PageUpdater
*/
- public function newPageUpdater( User $user ) {
+ public function newPageUpdater( User $user, RevisionSlotsUpdate $forUpdate = null ) {
global $wgAjaxEditStash, $wgUseAutomaticEditSummaries, $wgPageCreationLog;
$pageUpdater = new PageUpdater(
$user,
$this, // NOTE: eventually, PageUpdater should not know about WikiPage
- $this->getDerivedDataUpdater( $user ),
+ $this->getDerivedDataUpdater( $user, null, $forUpdate, true ),
$this->getDBLoadBalancer(),
$this->getRevisionStore()
);
$flags = ( $flags & ~EDIT_MINOR );
}
+ $slotsUpdate = new RevisionSlotsUpdate();
+ $slotsUpdate->modifyContent( 'main', $content );
+
// NOTE: while doEditContent() executes, callbacks to getDerivedDataUpdater and
// prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
// used by this PageUpdater. However, there is no guarantee for this.
- $updater = $this->newPageUpdater( $user );
+ $updater = $this->newPageUpdater( $user, $slotsUpdate );
$updater->setContent( 'main', $content );
$updater->setOriginalRevisionId( $originalRevId );
$updater->setUndidRevisionId( $undidRevId );
}
--$contextlines;
// truncate function changes ... to relevant i18n message.
- $pre = $wgContLang->truncate( $m[1], - $contextchars, '...', false );
+ $pre = $wgContLang->truncateForVisual( $m[1], - $contextchars, '...', false );
if ( count( $m ) < 3 ) {
$post = '';
} else {
- $post = $wgContLang->truncate( $m[3], $contextchars, '...', false );
+ $post = $wgContLang->truncateForVisual( $m[3], $contextchars, '...', false );
}
$found = $m[2];
CONTENT_MODEL_WIKITEXT
);
+ $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user1 );
+
$status = $page->doEditContent( $content, "[[testing]] 1", EDIT_NEW, false, $user1 );
$this->assertTrue( $status->isOK(), 'OK' );
$this->assertTrue( $status->value['revision']->getContent()->equals( $content ), 'equals' );
$rev = $page->getRevision();
+ $preparedEditAfter = $page->prepareContentForEdit( $content, $rev, $user1 );
+
$this->assertNotNull( $rev->getRecentChange() );
$this->assertSame( $rev->getId(), (int)$rev->getRecentChange()->getAttribute( 'rc_this_oldid' ) );
+ // make sure that cached ParserOutput gets re-used throughout
+ $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
+
$id = $page->getId();
// Test page creation logging
$this->assertEquals( 2, $n, 'pagelinks should contain two links from the page' );
}
+ /**
+ * @covers WikiPage::doEditContent
+ */
+ public function testDoEditContent_twice() {
+ $title = Title::newFromText( __METHOD__ );
+ $page = WikiPage::factory( $title );
+ $content = ContentHandler::makeContent( '$1 van $2', $title );
+
+ // Make sure we can do the exact same save twice.
+ // This tests checks that internal caches are reset as appropriate.
+ $status1 = $page->doEditContent( $content, __METHOD__ );
+ $status2 = $page->doEditContent( $content, __METHOD__ );
+
+ $this->assertTrue( $status1->isOK(), 'OK' );
+ $this->assertTrue( $status2->isOK(), 'OK' );
+
+ $this->assertTrue( isset( $status1->value['revision'] ), 'OK' );
+ $this->assertFalse( isset( $status2->value['revision'] ), 'OK' );
+ }
+
/**
* Undeletion is covered in PageArchiveTest::testUndeleteRevisions()
* TODO: Revision deletion
->method( 'getParserOutput' )
->willReturn( new ParserOutput( 'HTML' ) );
- $updater = $page->newPageUpdater( $user );
+ $preparedEditBefore = $page->prepareContentForEdit( $content, null, $user );
+
+ // provide context, so the cache can be kept in place
+ $slotsUpdate = new revisionSlotsUpdate();
+ $slotsUpdate->modifyContent( 'main', $content );
+
+ $updater = $page->newPageUpdater( $user, $slotsUpdate );
$updater->setContent( 'main', $content );
$revision = $updater->saveRevision(
CommentStoreComment::newUnsavedComment( 'test' ),
EDIT_NEW
);
+ $preparedEditAfter = $page->prepareContentForEdit( $content, $revision, $user );
+
$this->assertSame( $revision->getId(), $page->getLatest() );
+
+ // Parsed output must remain cached throughout.
+ $this->assertSame( $preparedEditBefore->output, $preparedEditAfter->output );
}
/**
$updater1->prepareUpdate( $revision );
- // Re-use updater with same revision or content
+ // Re-use updater with same revision or content, even if base changed
$this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, $revision ) );
$slotsUpdate = RevisionSlotsUpdate::newFromContent(
);
$this->assertSame( $updater1, $page->getDerivedDataUpdater( $user, null, $slotsUpdate ) );
+ // Don't re-use for edit if base revision ID changed
+ $this->assertNotSame(
+ $updater1,
+ $page->getDerivedDataUpdater( $user, null, $slotsUpdate, true )
+ );
+
// Don't re-use with different user
$updater2a = $page->getDerivedDataUpdater( $admin, null, $slotsUpdate );
$updater2a->prepareContent( $admin, $slotsUpdate, false );