* @return string HTML
*/
private function pagingLinks( $first, $last, $type = '' ) {
- $prevLink = $this->msg( 'prev-page' )->text();
+ $prevLink = $this->msg( 'prev-page' )->escaped();
$linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
if ( $first != '' ) {
unset( $prevQuery["{$type}from"] );
$prevLink = $linkRenderer->makeKnownLink(
$this->addFragmentToTitle( $this->title, $type ),
- $prevLink,
+ new HtmlArmor( $prevLink ),
[],
$prevQuery
);
}
- $nextLink = $this->msg( 'next-page' )->text();
+ $nextLink = $this->msg( 'next-page' )->escaped();
if ( $last != '' ) {
$lastQuery = $this->query;
unset( $lastQuery["{$type}until"] );
$nextLink = $linkRenderer->makeKnownLink(
$this->addFragmentToTitle( $this->title, $type ),
- $nextLink,
+ new HtmlArmor( $nextLink ),
[],
$lastQuery
);
*/
class CommentStore {
- /** Maximum length of a comment. Longer comments will be truncated. */
+ /**
+ * Maximum length of a comment in UTF-8 characters. Longer comments will be truncated.
+ * @note This must be at least 255 and not greater than floor( MAX_COMMENT_LENGTH / 4 ).
+ */
+ const COMMENT_CHARACTER_LIMIT = 1000;
+
+ /**
+ * Maximum length of a comment in bytes. Longer comments will be truncated.
+ * @note This value is determined by the size of the underlying database field,
+ * currently BLOB in MySQL/MariaDB.
+ */
const MAX_COMMENT_LENGTH = 65535;
- /** Maximum length of serialized data. Longer data will result in an exception. */
+ /**
+ * Maximum length of serialized data in bytes. Longer data will result in an exception.
+ * @note This value is determined by the size of the underlying database field,
+ * currently BLOB in MySQL/MariaDB.
+ */
const MAX_DATA_LENGTH = 65535;
/**
# 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;
+ }
if ( $this->stage > MIGRATION_OLD && !$comment->id ) {
$dbData = $comment->data;
* @deprecated since 1.30 use MediaWiki\Shell::isDisabled()
*/
function wfShellExecDisabled() {
+ wfDeprecated( __FUNCTION__, '1.30' );
return Shell::isDisabled() ? 'disabled' : false;
}
[ 'addPgIndex', 'user_groups', 'user_groups_expiry', '( ug_expiry )' ],
// 1.30
- [ 'modifyField', 'image', 'img_media_type', 'patch-add-3d.sql' ],
+ [ 'addPgEnumValue', 'media_type', '3D' ],
[ 'setDefault', 'revision', 'rev_comment', '' ],
[ 'changeNullableField', 'revision', 'rev_comment', 'NOT NULL', true ],
[ 'setDefault', 'archive', 'ar_comment', '' ],
}
}
+ /**
+ * Add a value to an existing PostgreSQL enum type
+ * @since 1.31
+ * @param string $type Type name. Must be in the core schema.
+ * @param string $value Value to add.
+ */
+ public function addPgEnumValue( $type, $value ) {
+ $row = $this->db->selectRow(
+ [
+ 't' => 'pg_catalog.pg_type',
+ 'n' => 'pg_catalog.pg_namespace',
+ 'e' => 'pg_catalog.pg_enum',
+ ],
+ [ 't.typname', 't.typtype', 'e.enumlabel' ],
+ [
+ 't.typname' => $type,
+ 'n.nspname' => $this->db->getCoreSchema(),
+ ],
+ __METHOD__,
+ [],
+ [
+ 'n' => [ 'JOIN', 't.typnamespace = n.oid' ],
+ 'e' => [ 'LEFT JOIN', [ 'e.enumtypid = t.oid', 'e.enumlabel' => $value ] ],
+ ]
+ );
+
+ if ( !$row ) {
+ $this->output( "...Type $type does not exist, skipping modify enum.\n" );
+ } elseif ( $row->typtype !== 'e' ) {
+ $this->output( "...Type $type does not seem to be an enum, skipping modify enum.\n" );
+ } elseif ( $row->enumlabel === $value ) {
+ $this->output( "...Enum type $type already contains value '$value'.\n" );
+ } else {
+ $this->output( "...Adding value '$value' to enum type $type.\n" );
+ $etype = $this->db->addIdentifierQuotes( $type );
+ $evalue = $this->db->addQuotes( $value );
+ $this->db->query( "ALTER TYPE $etype ADD VALUE $evalue" );
+ }
+ }
+
protected function dropFkey( $table, $field ) {
$fi = $this->db->fieldInfo( $table, $field );
if ( is_null( $fi ) ) {
+++ /dev/null
-ALTER TYPE media_type ADD VALUE '3D';
public function testInsertTruncation() {
$comment = str_repeat( '💣', 16400 );
$truncated1 = str_repeat( '💣', 63 ) . '...';
- $truncated2 = str_repeat( '💣', 16383 ) . '...';
+ $truncated2 = str_repeat( '💣', CommentStore::COMMENT_CHARACTER_LIMIT - 3 ) . '...';
$store = $this->makeStore( MIGRATION_WRITE_BOTH, 'ipb_reason' );
$fields = $store->insert( $this->db, $comment );
}
protected function setUp() {
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+ global $wgContLang;
parent::setUp();
- $wgExtraNamespaces[12312] = 'Dummy';
- $wgExtraNamespaces[12313] = 'Dummy_talk';
+ $this->mergeMwGlobalArrayValue(
+ 'wgExtraNamespaces',
+ [
+ 12312 => 'Dummy',
+ 12313 => 'Dummy_talk',
+ ]
+ );
- $wgNamespaceContentModels[12312] = 'DUMMY';
- $wgContentHandlers['DUMMY'] = 'DummyContentHandlerForTesting';
+ $this->mergeMwGlobalArrayValue(
+ 'wgNamespaceContentModels',
+ [
+ 12312 => 'DUMMY',
+ ]
+ );
+
+ $this->mergeMwGlobalArrayValue(
+ 'wgContentHandlers',
+ [
+ 'DUMMY' => 'DummyContentHandlerForTesting',
+ ]
+ );
MWNamespace::clearCaches();
// Reset namespace cache
}
protected function tearDown() {
- global $wgExtraNamespaces, $wgNamespaceContentModels, $wgContentHandlers, $wgContLang;
+ global $wgContLang;
parent::tearDown();
- unset( $wgExtraNamespaces[12312] );
- unset( $wgExtraNamespaces[12313] );
-
- unset( $wgNamespaceContentModels[12312] );
- unset( $wgContentHandlers['DUMMY'] );
-
MWNamespace::clearCaches();
// Reset namespace cache
$wgContLang->resetNamespaces();
return $rev;
}
- protected function createPage( $page, $text, $model = null ) {
- if ( is_string( $page ) ) {
- if ( !preg_match( '/:/', $page ) &&
- ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
- ) {
- $ns = $this->getDefaultWikitextNS();
- $page = MWNamespace::getCanonicalName( $ns ) . ':' . $page;
- }
-
- $page = Title::newFromText( $page );
+ /**
+ * @param string $titleString
+ * @param string $text
+ * @param string|null $model
+ *
+ * @return WikiPage
+ */
+ protected function createPage( $titleString, $text, $model = null ) {
+ if ( !preg_match( '/:/', $titleString ) &&
+ ( $model === null || $model === CONTENT_MODEL_WIKITEXT )
+ ) {
+ $ns = $this->getDefaultWikitextNS();
+ $titleString = MWNamespace::getCanonicalName( $ns ) . ':' . $titleString;
}
- if ( $page instanceof Title ) {
- $page = new WikiPage( $page );
- }
+ $title = Title::newFromText( $titleString );
+ $wikipage = new WikiPage( $title );
- if ( $page->exists() ) {
- $page->doDeleteArticle( "done" );
+ // Delete the article if it already exists
+ if ( $wikipage->exists() ) {
+ $wikipage->doDeleteArticle( "done" );
}
- $content = ContentHandler::makeContent( $text, $page->getTitle(), $model );
- $page->doEditContent( $content, "testing", EDIT_NEW );
+ $content = ContentHandler::makeContent( $text, $title, $model );
+ $wikipage->doEditContent( $content, __METHOD__, EDIT_NEW );
- return $page;
+ return $wikipage;
}
protected function assertRevEquals( Revision $orig, Revision $rev = null ) {
$this->assertRevEquals( $orig, $rev );
}
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withoutId() {
+ $page = $this->createPage(
+ __METHOD__,
+ 'GOAT',
+ CONTENT_MODEL_WIKITEXT
+ );
+ $latestRevId = $page->getLatest();
+
+ $rev = Revision::newFromTitle( $page->getTitle() );
+
+ $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
+ $this->assertEquals( $latestRevId, $rev->getId() );
+ }
+
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withId() {
+ $page = $this->createPage(
+ __METHOD__,
+ 'GOAT',
+ CONTENT_MODEL_WIKITEXT
+ );
+ $latestRevId = $page->getLatest();
+
+ $rev = Revision::newFromTitle( $page->getTitle(), $latestRevId );
+
+ $this->assertTrue( $page->getTitle()->equals( $rev->getTitle() ) );
+ $this->assertEquals( $latestRevId, $rev->getId() );
+ }
+
+ /**
+ * @covers Revision::newFromTitle
+ */
+ public function testNewFromTitle_withBadId() {
+ $page = $this->createPage(
+ __METHOD__,
+ 'GOAT',
+ CONTENT_MODEL_WIKITEXT
+ );
+ $latestRevId = $page->getLatest();
+
+ $rev = Revision::newFromTitle( $page->getTitle(), $latestRevId + 1 );
+
+ $this->assertNull( $rev );
+ }
+
/**
* @covers Revision::newFromRow
*/
+++ /dev/null
-<?php
-
-/**
- * @group ContentHandler
- * @group Database
- * ^--- important, causes temporary tables to be used instead of the real database
- */
-class RevisionTestContentHandlerUseDB extends RevisionStorageTest {
-
- protected function setUp() {
- $this->setMwGlobals( 'wgContentHandlerUseDB', false );
-
- $dbw = wfGetDB( DB_MASTER );
-
- $page_table = $dbw->tableName( 'page' );
- $revision_table = $dbw->tableName( 'revision' );
- $archive_table = $dbw->tableName( 'archive' );
-
- if ( $dbw->fieldExists( $page_table, 'page_content_model' ) ) {
- $dbw->query( "alter table $page_table drop column page_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_model" );
- $dbw->query( "alter table $revision_table drop column rev_content_format" );
- $dbw->query( "alter table $archive_table drop column ar_content_model" );
- $dbw->query( "alter table $archive_table drop column ar_content_format" );
- }
-
- parent::setUp();
- }
-
- /**
- * @covers Revision::selectFields
- */
- public function testSelectFields() {
- $fields = Revision::selectFields();
-
- $this->assertTrue( in_array( 'rev_id', $fields ), 'missing rev_id in list of fields' );
- $this->assertTrue( in_array( 'rev_page', $fields ), 'missing rev_page in list of fields' );
- $this->assertTrue(
- in_array( 'rev_timestamp', $fields ),
- 'missing rev_timestamp in list of fields'
- );
- $this->assertTrue( in_array( 'rev_user', $fields ), 'missing rev_user in list of fields' );
-
- $this->assertFalse(
- in_array( 'rev_content_model', $fields ),
- 'missing rev_content_model in list of fields'
- );
- $this->assertFalse(
- in_array( 'rev_content_format', $fields ),
- 'missing rev_content_format in list of fields'
- );
- }
-
- /**
- * @covers Revision::getContentModel
- */
- public function testGetContentModel() {
- try {
- $this->makeRevision( [ 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT ] );
-
- $this->fail( "Creating JavaScript content on a wikitext page should fail with "
- . "\$wgContentHandlerUseDB disabled" );
- } catch ( MWException $ex ) {
- $this->assertTrue( true ); // ok
- }
- }
-
- /**
- * @covers Revision::getContentFormat
- */
- public function testGetContentFormat() {
- try {
- // @todo change this to test failure on using a non-standard (but supported) format
- // for a content model supported in the given location. As of 1.21, there are
- // no alternative formats for any of the standard content models that could be
- // used for this though.
-
- $this->makeRevision( [ 'text' => 'hello hello.',
- 'content_model' => CONTENT_MODEL_JAVASCRIPT,
- 'content_format' => 'text/javascript' ] );
-
- $this->fail( "Creating JavaScript content on a wikitext page should fail with "
- . "\$wgContentHandlerUseDB disabled" );
- } catch ( MWException $ex ) {
- $this->assertTrue( true ); // ok
- }
- }
-}