From: jenkins-bot Date: Thu, 22 Feb 2018 05:15:34 +0000 (+0000) Subject: Merge "Move $.byteLength and $.trimByteLength to new module 'mediawiki.String'" X-Git-Tag: 1.31.0-rc.0~544 X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=commitdiff_plain;h=59954a8ea051d526376cbc50fc994dd28e827fea;hp=f19c9021a466f414065db6f74257efc702325110 Merge "Move $.byteLength and $.trimByteLength to new module 'mediawiki.String'" --- diff --git a/.gitignore b/.gitignore index bb3a946593..0112cf31a6 100644 --- a/.gitignore +++ b/.gitignore @@ -35,6 +35,8 @@ sftp-config.json /images/timeline ## Extension:Score /images/lilypond +## Extension:TimedMediaHandler +/images/transcoded /images/tmp /maintenance/.mweval_history /maintenance/.mwsql_history diff --git a/RELEASE-NOTES-1.31 b/RELEASE-NOTES-1.31 index 2c071b53b0..f49a5822e1 100644 --- a/RELEASE-NOTES-1.31 +++ b/RELEASE-NOTES-1.31 @@ -106,6 +106,7 @@ changes to languages because of Phabricator reports. * (T187750) New language support: Spanish formal address (es-formal). === Other changes in 1.31 === +* Browser support for Internet Explorer 10 was lowered from Grade A to Grade C. * Introducing multi-content-revision capability into the storage layer. For details, see . * The Revision class was deprecated in favor of RevisionStore, BlobStore, and diff --git a/autoload.php b/autoload.php index 9042f7b808..7f90d4753e 100644 --- a/autoload.php +++ b/autoload.php @@ -647,6 +647,10 @@ $wgAutoloadLocalClasses = [ 'ImportStringSource' => __DIR__ . '/includes/import/ImportStringSource.php', 'ImportTextFiles' => __DIR__ . '/maintenance/importTextFiles.php', 'ImportTitleFactory' => __DIR__ . '/includes/title/ImportTitleFactory.php', + 'ImportableOldRevision' => __DIR__ . '/includes/import/ImportableOldRevision.php', + 'ImportableOldRevisionImporter' => __DIR__ . '/includes/import/ImportableOldRevisionImporter.php', + 'ImportableUploadRevision' => __DIR__ . '/includes/import/ImportableUploadRevision.php', + 'ImportableUploadRevisionImporter' => __DIR__ . '/includes/import/ImportableUploadRevisionImporter.php', 'IncludableSpecialPage' => __DIR__ . '/includes/specialpage/IncludableSpecialPage.php', 'IndexPager' => __DIR__ . '/includes/pager/IndexPager.php', 'InfoAction' => __DIR__ . '/includes/actions/InfoAction.php', @@ -1075,6 +1079,7 @@ $wgAutoloadLocalClasses = [ 'ObjectFactory' => __DIR__ . '/includes/compat/ObjectFactory.php', 'OldChangesList' => __DIR__ . '/includes/changes/OldChangesList.php', 'OldLocalFile' => __DIR__ . '/includes/filerepo/file/OldLocalFile.php', + 'OldRevisionImporter' => __DIR__ . '/includes/import/OldRevisionImporter.php', 'OracleInstaller' => __DIR__ . '/includes/installer/OracleInstaller.php', 'OracleUpdater' => __DIR__ . '/includes/installer/OracleUpdater.php', 'OrderedStreamingForkController' => __DIR__ . '/includes/OrderedStreamingForkController.php', @@ -1570,6 +1575,7 @@ $wgAutoloadLocalClasses = [ 'UploadFromStash' => __DIR__ . '/includes/upload/UploadFromStash.php', 'UploadFromUrl' => __DIR__ . '/includes/upload/UploadFromUrl.php', 'UploadLogFormatter' => __DIR__ . '/includes/logging/UploadLogFormatter.php', + 'UploadRevisionImporter' => __DIR__ . '/includes/import/UploadRevisionImporter.php', 'UploadSourceAdapter' => __DIR__ . '/includes/import/UploadSourceAdapter.php', 'UploadSourceField' => __DIR__ . '/includes/specials/formfields/UploadSourceField.php', 'UploadStash' => __DIR__ . '/includes/upload/UploadStash.php', diff --git a/includes/MediaWikiServices.php b/includes/MediaWikiServices.php index 9077666ddf..59f194d793 100644 --- a/includes/MediaWikiServices.php +++ b/includes/MediaWikiServices.php @@ -690,6 +690,30 @@ class MediaWikiServices extends ServiceContainer { return $this->getService( 'ReadOnlyMode' ); } + /** + * @since 1.31 + * @return \UploadRevisionImporter + */ + public function getWikiRevisionUploadImporter() { + return $this->getService( 'UploadRevisionImporter' ); + } + + /** + * @since 1.31 + * @return \OldRevisionImporter + */ + public function getWikiRevisionOldRevisionImporter() { + return $this->getService( 'OldRevisionImporter' ); + } + + /** + * @since 1.31 + * @return \OldRevisionImporter + */ + public function getWikiRevisionOldRevisionImporterNoUpdates() { + return $this->getService( 'WikiRevisionOldRevisionImporterNoUpdates' ); + } + /** * @since 1.30 * @return CommandFactory diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index 8b0452db3d..dab9fb9955 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -442,6 +442,29 @@ return [ ); }, + 'UploadRevisionImporter' => function ( MediaWikiServices $services ) { + return new ImportableUploadRevisionImporter( + $services->getMainConfig()->get( 'EnableUploads' ), + LoggerFactory::getInstance( 'UploadRevisionImporter' ) + ); + }, + + 'OldRevisionImporter' => function ( MediaWikiServices $services ) { + return new ImportableOldRevisionImporter( + true, + LoggerFactory::getInstance( 'OldRevisionImporter' ), + $services->getDBLoadBalancer() + ); + }, + + 'WikiRevisionOldRevisionImporterNoUpdates' => function ( MediaWikiServices $services ) { + return new ImportableOldRevisionImporter( + false, + LoggerFactory::getInstance( 'OldRevisionImporter' ), + $services->getDBLoadBalancer() + ); + }, + 'ShellCommandFactory' => function ( MediaWikiServices $services ) { $config = $services->getMainConfig(); diff --git a/includes/changetags/ChangeTags.php b/includes/changetags/ChangeTags.php index 7e4dd006ac..b30b82df1d 100644 --- a/includes/changetags/ChangeTags.php +++ b/includes/changetags/ChangeTags.php @@ -181,6 +181,28 @@ class ChangeTags { return $msg; } + /** + * Get truncated message for the tag's long description. + * + * @param string $tag Tag name. + * @param int $length Maximum length of truncated message, including ellipsis. + * @param IContextSource $context + * + * @return string Truncated long tag description. + */ + public static function truncateTagDescription( $tag, $length, IContextSource $context ) { + $originalDesc = self::tagLongDescriptionMessage( $tag, $context ); + // If there is no tag description, return empty string + if ( !$originalDesc ) { + return ''; + } + + $taglessDesc = Sanitizer::stripAllTags( $originalDesc->parse() ); + $escapedDesc = Sanitizer::escapeHtmlAllowEntities( $taglessDesc ); + + return $context->getLanguage()->truncateForVisual( $escapedDesc, $length ); + } + /** * Add tags to a change given its rc_id, rev_id and/or log_id * diff --git a/includes/import/ImportableOldRevision.php b/includes/import/ImportableOldRevision.php new file mode 100644 index 0000000000..6d1e24264c --- /dev/null +++ b/includes/import/ImportableOldRevision.php @@ -0,0 +1,68 @@ +doUpdates = $doUpdates; + $this->logger = $logger; + $this->loadBalancer = $loadBalancer; + } + + public function import( ImportableOldRevision $importableRevision, $doUpdates = true ) { + $dbw = $this->loadBalancer->getConnectionRef( DB_MASTER ); + + # Sneak a single revision into place + $user = $importableRevision->getUserObj() ?: User::newFromName( $importableRevision->getUser() ); + if ( $user ) { + $userId = intval( $user->getId() ); + $userText = $user->getName(); + } else { + $userId = 0; + $userText = $importableRevision->getUser(); + $user = new User; + } + + // avoid memory leak...? + Title::clearCaches(); + + $page = WikiPage::factory( $importableRevision->getTitle() ); + $page->loadPageData( 'fromdbmaster' ); + if ( !$page->exists() ) { + // must create the page... + $pageId = $page->insertOn( $dbw ); + $created = true; + $oldcountable = null; + } else { + $pageId = $page->getId(); + $created = false; + + // Note: sha1 has been in XML dumps since 2012. If you have an + // older dump, the duplicate detection here won't work. + $prior = $dbw->selectField( 'revision', '1', + [ 'rev_page' => $pageId, + 'rev_timestamp' => $dbw->timestamp( $importableRevision->getTimestamp() ), + 'rev_sha1' => $importableRevision->getSha1Base36() ], + __METHOD__ + ); + if ( $prior ) { + // @todo FIXME: This could fail slightly for multiple matches :P + $this->logger->debug( __METHOD__ . ": skipping existing revision for [[" . + $importableRevision->getTitle()->getPrefixedText() . "]], timestamp " . + $importableRevision->getTimestamp() . "\n" ); + return false; + } + } + + if ( !$pageId ) { + // This seems to happen if two clients simultaneously try to import the + // same page + $this->logger->debug( __METHOD__ . ': got invalid $pageId when importing revision of [[' . + $importableRevision->getTitle()->getPrefixedText() . ']], timestamp ' . + $importableRevision->getTimestamp() . "\n" ); + return false; + } + + // Select previous version to make size diffs correct + // @todo This assumes that multiple revisions of the same page are imported + // in order from oldest to newest. + $prevId = $dbw->selectField( 'revision', 'rev_id', + [ + 'rev_page' => $pageId, + 'rev_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $importableRevision->getTimestamp() ) ), + ], + __METHOD__, + [ 'ORDER BY' => [ + 'rev_timestamp DESC', + 'rev_id DESC', // timestamp is not unique per page + ] + ] + ); + + # @todo FIXME: Use original rev_id optionally (better for backups) + # Insert the row + $revision = new Revision( [ + 'title' => $importableRevision->getTitle(), + 'page' => $pageId, + 'content_model' => $importableRevision->getModel(), + 'content_format' => $importableRevision->getFormat(), + // XXX: just set 'content' => $wikiRevision->getContent()? + 'text' => $importableRevision->getContent()->serialize( $importableRevision->getFormat() ), + 'comment' => $importableRevision->getComment(), + 'user' => $userId, + 'user_text' => $userText, + 'timestamp' => $importableRevision->getTimestamp(), + 'minor_edit' => $importableRevision->getMinor(), + 'parent_id' => $prevId, + ] ); + $revision->insertOn( $dbw ); + $changed = $page->updateIfNewerOn( $dbw, $revision ); + + if ( $changed !== false && $this->doUpdates ) { + $this->logger->debug( __METHOD__ . ": running updates\n" ); + // countable/oldcountable stuff is handled in WikiImporter::finishImportPage + $page->doEditUpdates( + $revision, + $user, + [ 'created' => $created, 'oldcountable' => 'no-change' ] + ); + } + + return true; + } + +} diff --git a/includes/import/ImportableUploadRevision.php b/includes/import/ImportableUploadRevision.php new file mode 100644 index 0000000000..3f60112a0f --- /dev/null +++ b/includes/import/ImportableUploadRevision.php @@ -0,0 +1,68 @@ +enableUploads = $enableUploads; + $this->logger = $logger; + } + + /** + * @return StatusValue + */ + private function newNotOkStatus() { + $statusValue = new StatusValue(); + $statusValue->setOK( false ); + return $statusValue; + } + + public function import( ImportableUploadRevision $importableRevision ) { + # Construct a file + $archiveName = $importableRevision->getArchiveName(); + if ( $archiveName ) { + $this->logger->debug( __METHOD__ . "Importing archived file as $archiveName\n" ); + $file = OldLocalFile::newFromArchiveName( $importableRevision->getTitle(), + RepoGroup::singleton()->getLocalRepo(), $archiveName ); + } else { + $file = wfLocalFile( $importableRevision->getTitle() ); + $file->load( File::READ_LATEST ); + $this->logger->debug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" ); + if ( $file->exists() && $file->getTimestamp() > $importableRevision->getTimestamp() ) { + $archiveName = $file->getTimestamp() . '!' . $file->getName(); + $file = OldLocalFile::newFromArchiveName( $importableRevision->getTitle(), + RepoGroup::singleton()->getLocalRepo(), $archiveName ); + $this->logger->debug( __METHOD__ . "File already exists; importing as $archiveName\n" ); + } + } + if ( !$file ) { + $this->logger->debug( __METHOD__ . ': Bad file for ' . $importableRevision->getTitle() . "\n" ); + return $this->newNotOkStatus(); + } + + # Get the file source or download if necessary + $source = $importableRevision->getFileSrc(); + $autoDeleteSource = $importableRevision->isTempSrc(); + if ( !strlen( $source ) ) { + $source = $this->downloadSource( $importableRevision ); + $autoDeleteSource = true; + } + if ( !strlen( $source ) ) { + $this->logger->debug( __METHOD__ . ": Could not fetch remote file.\n" ); + return $this->newNotOkStatus(); + } + + $tmpFile = new TempFSFile( $source ); + if ( $autoDeleteSource ) { + $tmpFile->autocollect(); + } + + $sha1File = ltrim( sha1_file( $source ), '0' ); + $sha1 = $importableRevision->getSha1(); + if ( $sha1 && ( $sha1 !== $sha1File ) ) { + $this->logger->debug( __METHOD__ . ": Corrupt file $source.\n" ); + return $this->newNotOkStatus(); + } + + $user = $importableRevision->getUserObj() ?: User::newFromName( $importableRevision->getUser() ); + + # Do the actual upload + if ( $archiveName ) { + $status = $file->uploadOld( $source, $archiveName, + $importableRevision->getTimestamp(), $importableRevision->getComment(), $user ); + } else { + $flags = 0; + $status = $file->upload( + $source, + $importableRevision->getComment(), + $importableRevision->getComment(), + $flags, + false, + $importableRevision->getTimestamp(), + $user + ); + } + + if ( $status->isGood() ) { + $this->logger->debug( __METHOD__ . ": Successful\n" ); + } else { + $this->logger->debug( __METHOD__ . ': failed: ' . $status->getHTML() . "\n" ); + } + + return $status; + } + + /** + * @deprecated DO NOT CALL ME. + * This method was introduced when factoring UploadImporter out of WikiRevision. + * It only has 1 use by the deprecated downloadSource method in WikiRevision. + * Do not use this in new code. + * + * @param ImportableUploadRevision $wikiRevision + * + * @return bool|string + */ + public function downloadSource( ImportableUploadRevision $wikiRevision ) { + if ( !$this->enableUploads ) { + return false; + } + + $tempo = tempnam( wfTempDir(), 'download' ); + $f = fopen( $tempo, 'wb' ); + if ( !$f ) { + $this->logger->debug( "IMPORT: couldn't write to temp file $tempo\n" ); + return false; + } + + // @todo FIXME! + $src = $wikiRevision->getSrc(); + $data = Http::get( $src, [], __METHOD__ ); + if ( !$data ) { + $this->logger->debug( "IMPORT: couldn't fetch source $src\n" ); + fclose( $f ); + unlink( $tempo ); + return false; + } + + fwrite( $f, $data ); + fclose( $f ); + + return $tempo; + } + +} diff --git a/includes/import/OldRevisionImporter.php b/includes/import/OldRevisionImporter.php new file mode 100644 index 0000000000..72af43b918 --- /dev/null +++ b/includes/import/OldRevisionImporter.php @@ -0,0 +1,17 @@ +src = $src; @@ -494,7 +496,7 @@ class WikiRevision { /** * @since 1.12.2 - * @return mixed + * @return string|null */ public function getSrc() { return $this->src; @@ -511,6 +513,17 @@ class WikiRevision { return false; } + /** + * @since 1.31 + * @return bool|string + */ + public function getSha1Base36() { + if ( $this->sha1base36 ) { + return $this->sha1base36; + } + return false; + } + /** * @since 1.17 * @return string @@ -577,106 +590,16 @@ class WikiRevision { /** * @since 1.4.1 + * @deprecated in 1.31. Use OldRevisionImporter::import * @return bool */ public function importOldRevision() { - $dbw = wfGetDB( DB_MASTER ); - - # Sneak a single revision into place - $user = $this->getUserObj() ?: User::newFromName( $this->getUser() ); - if ( $user ) { - $userId = intval( $user->getId() ); - $userText = $user->getName(); - } else { - $userId = 0; - $userText = $this->getUser(); - $user = new User; - } - - // avoid memory leak...? - Title::clearCaches(); - - $page = WikiPage::factory( $this->title ); - $page->loadPageData( 'fromdbmaster' ); - if ( !$page->exists() ) { - // must create the page... - $pageId = $page->insertOn( $dbw ); - $created = true; - $oldcountable = null; + if ( $this->mNoUpdates ) { + $importer = MediaWikiServices::getInstance()->getWikiRevisionOldRevisionImporterNoUpdates(); } else { - $pageId = $page->getId(); - $created = false; - - // Note: sha1 has been in XML dumps since 2012. If you have an - // older dump, the duplicate detection here won't work. - $prior = $dbw->selectField( 'revision', '1', - [ 'rev_page' => $pageId, - 'rev_timestamp' => $dbw->timestamp( $this->timestamp ), - 'rev_sha1' => $this->sha1base36 ], - __METHOD__ - ); - if ( $prior ) { - // @todo FIXME: This could fail slightly for multiple matches :P - wfDebug( __METHOD__ . ": skipping existing revision for [[" . - $this->title->getPrefixedText() . "]], timestamp " . $this->timestamp . "\n" ); - return false; - } - } - - if ( !$pageId ) { - // This seems to happen if two clients simultaneously try to import the - // same page - wfDebug( __METHOD__ . ': got invalid $pageId when importing revision of [[' . - $this->title->getPrefixedText() . ']], timestamp ' . $this->timestamp . "\n" ); - return false; + $importer = MediaWikiServices::getInstance()->getWikiRevisionOldRevisionImporter(); } - - // Select previous version to make size diffs correct - // @todo This assumes that multiple revisions of the same page are imported - // in order from oldest to newest. - $prevId = $dbw->selectField( 'revision', 'rev_id', - [ - 'rev_page' => $pageId, - 'rev_timestamp <= ' . $dbw->addQuotes( $dbw->timestamp( $this->timestamp ) ), - ], - __METHOD__, - [ 'ORDER BY' => [ - 'rev_timestamp DESC', - 'rev_id DESC', // timestamp is not unique per page - ] - ] - ); - - # @todo FIXME: Use original rev_id optionally (better for backups) - # Insert the row - $revision = new Revision( [ - 'title' => $this->title, - 'page' => $pageId, - 'content_model' => $this->getModel(), - 'content_format' => $this->getFormat(), - // XXX: just set 'content' => $this->getContent()? - 'text' => $this->getContent()->serialize( $this->getFormat() ), - 'comment' => $this->getComment(), - 'user' => $userId, - 'user_text' => $userText, - 'timestamp' => $this->timestamp, - 'minor_edit' => $this->minor, - 'parent_id' => $prevId, - ] ); - $revision->insertOn( $dbw ); - $changed = $page->updateIfNewerOn( $dbw, $revision ); - - if ( $changed !== false && !$this->mNoUpdates ) { - wfDebug( __METHOD__ . ": running updates\n" ); - // countable/oldcountable stuff is handled in WikiImporter::finishImportPage - $page->doEditUpdates( - $revision, - $user, - [ 'created' => $created, 'oldcountable' => 'no-change' ] - ); - } - - return true; + return $importer->import( $this ); } /** @@ -737,106 +660,26 @@ class WikiRevision { /** * @since 1.12.2 + * @deprecated in 1.31. Use UploadImporter::import * @return bool */ public function importUpload() { - # Construct a file - $archiveName = $this->getArchiveName(); - if ( $archiveName ) { - wfDebug( __METHOD__ . "Importing archived file as $archiveName\n" ); - $file = OldLocalFile::newFromArchiveName( $this->getTitle(), - RepoGroup::singleton()->getLocalRepo(), $archiveName ); - } else { - $file = wfLocalFile( $this->getTitle() ); - $file->load( File::READ_LATEST ); - wfDebug( __METHOD__ . 'Importing new file as ' . $file->getName() . "\n" ); - if ( $file->exists() && $file->getTimestamp() > $this->getTimestamp() ) { - $archiveName = $file->getTimestamp() . '!' . $file->getName(); - $file = OldLocalFile::newFromArchiveName( $this->getTitle(), - RepoGroup::singleton()->getLocalRepo(), $archiveName ); - wfDebug( __METHOD__ . "File already exists; importing as $archiveName\n" ); - } - } - if ( !$file ) { - wfDebug( __METHOD__ . ': Bad file for ' . $this->getTitle() . "\n" ); - return false; - } - - # Get the file source or download if necessary - $source = $this->getFileSrc(); - $autoDeleteSource = $this->isTempSrc(); - if ( !strlen( $source ) ) { - $source = $this->downloadSource(); - $autoDeleteSource = true; - } - if ( !strlen( $source ) ) { - wfDebug( __METHOD__ . ": Could not fetch remote file.\n" ); - return false; - } - - $tmpFile = new TempFSFile( $source ); - if ( $autoDeleteSource ) { - $tmpFile->autocollect(); - } - - $sha1File = ltrim( sha1_file( $source ), '0' ); - $sha1 = $this->getSha1(); - if ( $sha1 && ( $sha1 !== $sha1File ) ) { - wfDebug( __METHOD__ . ": Corrupt file $source.\n" ); - return false; - } - - $user = $this->getUserObj() ?: User::newFromName( $this->getUser() ); - - # Do the actual upload - if ( $archiveName ) { - $status = $file->uploadOld( $source, $archiveName, - $this->getTimestamp(), $this->getComment(), $user ); - } else { - $flags = 0; - $status = $file->upload( $source, $this->getComment(), $this->getComment(), - $flags, false, $this->getTimestamp(), $user ); - } - - if ( $status->isGood() ) { - wfDebug( __METHOD__ . ": Successful\n" ); - return true; - } else { - wfDebug( __METHOD__ . ': failed: ' . $status->getHTML() . "\n" ); - return false; - } + $importer = MediaWikiServices::getInstance()->getWikiRevisionUploadImporter(); + $statusValue = $importer->import( $this ); + return $statusValue->isGood(); } /** * @since 1.12.2 + * @deprecated in 1.31. Use UploadImporter::downloadSource * @return bool|string */ public function downloadSource() { - if ( !$this->config->get( 'EnableUploads' ) ) { - return false; - } - - $tempo = tempnam( wfTempDir(), 'download' ); - $f = fopen( $tempo, 'wb' ); - if ( !$f ) { - wfDebug( "IMPORT: couldn't write to temp file $tempo\n" ); - return false; - } - - // @todo FIXME! - $src = $this->getSrc(); - $data = Http::get( $src, [], __METHOD__ ); - if ( !$data ) { - wfDebug( "IMPORT: couldn't fetch source $src\n" ); - fclose( $f ); - unlink( $tempo ); - return false; - } - - fwrite( $f, $data ); - fclose( $f ); - - return $tempo; + $importer = new ImportableUploadRevisionImporter( + $this->config->get( 'EnableUploads' ), + LoggerFactory::getInstance( 'UploadRevisionImporter' ) + ); + return $importer->downloadSource( $this ); } } diff --git a/includes/libs/rdbms/database/Database.php b/includes/libs/rdbms/database/Database.php index 9a8996c7b3..572a798c06 100644 --- a/includes/libs/rdbms/database/Database.php +++ b/includes/libs/rdbms/database/Database.php @@ -2244,37 +2244,51 @@ abstract class Database implements IDatabase, IMaintainableDatabase, LoggerAware $rows = [ $rows ]; } - $affectedRowCount = 0; - foreach ( $rows as $row ) { - // Delete rows which collide with this one - $indexWhereClauses = []; - foreach ( $uniqueIndexes as $index ) { - $indexColumns = (array)$index; - $indexRowValues = array_intersect_key( $row, array_flip( $indexColumns ) ); - if ( count( $indexRowValues ) != count( $indexColumns ) ) { - throw new DBUnexpectedError( - $this, - 'New record does not provide all values for unique key (' . + $useTrx = !$this->trxLevel; + if ( $useTrx ) { + $this->begin( $fname, self::TRANSACTION_INTERNAL ); + } + try { + $affectedRowCount = 0; + foreach ( $rows as $row ) { + // Delete rows which collide with this one + $indexWhereClauses = []; + foreach ( $uniqueIndexes as $index ) { + $indexColumns = (array)$index; + $indexRowValues = array_intersect_key( $row, array_flip( $indexColumns ) ); + if ( count( $indexRowValues ) != count( $indexColumns ) ) { + throw new DBUnexpectedError( + $this, + 'New record does not provide all values for unique key (' . implode( ', ', $indexColumns ) . ')' - ); - } elseif ( in_array( null, $indexRowValues, true ) ) { - throw new DBUnexpectedError( - $this, - 'New record has a null value for unique key (' . + ); + } elseif ( in_array( null, $indexRowValues, true ) ) { + throw new DBUnexpectedError( + $this, + 'New record has a null value for unique key (' . implode( ', ', $indexColumns ) . ')' - ); + ); + } + $indexWhereClauses[] = $this->makeList( $indexRowValues, LIST_AND ); } - $indexWhereClauses[] = $this->makeList( $indexRowValues, LIST_AND ); - } - if ( $indexWhereClauses ) { - $this->delete( $table, $this->makeList( $indexWhereClauses, LIST_OR ), $fname ); + if ( $indexWhereClauses ) { + $this->delete( $table, $this->makeList( $indexWhereClauses, LIST_OR ), $fname ); + $affectedRowCount += $this->affectedRows(); + } + + // Now insert the row + $this->insert( $table, $row, $fname ); $affectedRowCount += $this->affectedRows(); } - - // Now insert the row - $this->insert( $table, $row, $fname ); - $affectedRowCount += $this->affectedRows(); + } catch ( Exception $e ) { + if ( $useTrx ) { + $this->rollback( $fname, self::FLUSHING_INTERNAL ); + } + throw $e; + } + if ( $useTrx ) { + $this->commit( $fname, self::FLUSHING_INTERNAL ); } $this->affectedRowCount = $affectedRowCount; diff --git a/includes/media/JpegMetadataExtractor.php b/includes/media/JpegMetadataExtractor.php index 229a891db8..0bd01cd6c9 100644 --- a/includes/media/JpegMetadataExtractor.php +++ b/includes/media/JpegMetadataExtractor.php @@ -82,7 +82,7 @@ class JpegMetadataExtractor { // this is just a sanity check throw new MWException( 'Too many jpeg segments. Aborting' ); } - while ( $buffer !== "\xFF" ) { + while ( $buffer !== "\xFF" && !feof( $fh ) ) { // In theory JPEG files are not allowed to contain anything between the sections, // but in practice they sometimes do. It's customary to ignore the garbage data. $buffer = fread( $fh, 1 ); diff --git a/includes/specialpage/ChangesListSpecialPage.php b/includes/specialpage/ChangesListSpecialPage.php index cf990c2839..5aa1c6bc31 100644 --- a/includes/specialpage/ChangesListSpecialPage.php +++ b/includes/specialpage/ChangesListSpecialPage.php @@ -33,6 +33,12 @@ use Wikimedia\Rdbms\IDatabase; * @ingroup SpecialPage */ abstract class ChangesListSpecialPage extends SpecialPage { + /** + * Maximum length of a tag description in UTF-8 characters. + * Longer descriptions will be truncated. + */ + const TAG_DESC_CHARACTER_LIMIT = 120; + /** * Preference name for saved queries. Subclasses that use saved queries should override this. * @var string @@ -794,15 +800,15 @@ abstract class ChangesListSpecialPage extends SpecialPage { isset( $explicitlyDefinedTags[ $tagName ] ) || isset( $softwareActivatedTags[ $tagName ] ) ) { - // Parse description - $desc = ChangeTags::tagLongDescriptionMessage( $tagName, $context ); - $result[] = [ 'name' => $tagName, 'label' => Sanitizer::stripAllTags( ChangeTags::tagDescription( $tagName, $context ) ), - 'description' => $desc ? Sanitizer::stripAllTags( $desc->parse() ) : '', + 'description' => + ChangeTags::truncateTagDescription( + $tagName, self::TAG_DESC_CHARACTER_LIMIT, $context + ), 'cssClass' => Sanitizer::escapeClass( 'mw-tag-' . $tagName ), 'hits' => $hits, ]; diff --git a/includes/specials/SpecialContributions.php b/includes/specials/SpecialContributions.php index 4775a7f928..806713b4ef 100644 --- a/includes/specials/SpecialContributions.php +++ b/includes/specials/SpecialContributions.php @@ -40,14 +40,12 @@ class SpecialContributions extends IncludableSpecialPage { $this->setHeaders(); $this->outputHeader(); $out = $this->getOutput(); + // Modules required for viewing the list of contributions (also when included on other pages) $out->addModuleStyles( [ 'mediawiki.special', 'mediawiki.special.changeslist', - 'mediawiki.widgets.DateInputWidget.styles', ] ); - $out->addModules( 'mediawiki.special.contributions' ); $this->addHelpLink( 'Help:User contributions' ); - $out->enableOOUI(); $this->opts = []; $request = $this->getRequest(); @@ -497,6 +495,14 @@ class SpecialContributions extends IncludableSpecialPage { $this->opts['hideMinor'] = false; } + // Modules required only for the form + $this->getOutput()->addModules( [ + 'mediawiki.userSuggest', + 'mediawiki.special.contributions', + ] ); + $this->getOutput()->addModuleStyles( 'mediawiki.widgets.DateInputWidget.styles' ); + $this->getOutput()->enableOOUI(); + $form = Html::openElement( 'form', [ @@ -544,8 +550,6 @@ class SpecialContributions extends IncludableSpecialPage { $filterSelection = Html::rawElement( 'div', [], '' ); } - $this->getOutput()->addModules( 'mediawiki.userSuggest' ); - $labelNewbies = Xml::radioLabel( $this->msg( 'sp-contributions-newbies' )->text(), 'contribs', diff --git a/languages/Language.php b/languages/Language.php index 084a2e76ae..fc8ef87c64 100644 --- a/languages/Language.php +++ b/languages/Language.php @@ -3472,27 +3472,103 @@ class Language { } /** - * Truncate a string to a specified length in bytes, appending an optional - * string (e.g. for ellipses) + * This method is deprecated since 1.31 and kept as alias for truncateForDatabase, which + * has replaced it. This method provides truncation suitable for DB. * * The database offers limited byte lengths for some columns in the database; * multi-byte character sets mean we need to ensure that only whole characters - * are included, otherwise broken characters can be passed to the user + * are included, otherwise broken characters can be passed to the user. * - * If $length is negative, the string will be truncated from the beginning + * @deprecated since 1.31, use truncateForDatabase or truncateForVisual as appropriate. * * @param string $string String to truncate - * @param int $length Maximum length (including ellipses) + * @param int $length Maximum length (including ellipsis) * @param string $ellipsis String to append to the truncated text * @param bool $adjustLength Subtract length of ellipsis from $length. * $adjustLength was introduced in 1.18, before that behaved as if false. * @return string */ function truncate( $string, $length, $ellipsis = '...', $adjustLength = true ) { + return $this->truncateForDatabase( $string, $length, $ellipsis, $adjustLength ); + } + + /** + * Truncate a string to a specified length in bytes, appending an optional + * string (e.g. for ellipsis) + * + * If $length is negative, the string will be truncated from the beginning + * + * @since 1.31 + * + * @param string $string String to truncate + * @param int $length Maximum length in bytes + * @param string $ellipsis String to append to the end of truncated text + * @param bool $adjustLength Subtract length of ellipsis from $length + * + * @return string + */ + function truncateForDatabase( $string, $length, $ellipsis = '...', $adjustLength = true ) { + return $this->truncateInternal( + $string, $length, $ellipsis, $adjustLength, 'strlen', 'substr' + ); + } + + /** + * Truncate a string to a specified number of characters, appending an optional + * string (e.g. for ellipsis). + * + * This provides multibyte version of truncate() method of this class, suitable for truncation + * based on number of characters, instead of number of bytes. + * + * If $length is negative, the string will be truncated from the beginning. + * + * @since 1.31 + * + * @param string $string String to truncate + * @param int $length Maximum number of characters + * @param string $ellipsis String to append to the end of truncated text + * @param bool $adjustLength Subtract length of ellipsis from $length + * + * @return string + */ + function truncateForVisual( $string, $length, $ellipsis = '...', $adjustLength = true ) { + // Passing encoding to mb_strlen and mb_substr is optional. + // Encoding defaults to mb_internal_encoding(), which is set to UTF-8 in Setup.php, so + // explicit specification of encoding is skipped. + // Note: Both multibyte methods are callables invoked in truncateInternal. + return $this->truncateInternal( + $string, $length, $ellipsis, $adjustLength, 'mb_strlen', 'mb_substr' + ); + } + + /** + * Internal method used for truncation. This method abstracts text truncation into + * one common method, allowing users to provide length measurement function and + * function for finding substring. + * + * For usages, see truncateForDatabase and truncateForVisual. + * + * @param string $string String to truncate + * @param int $length Maximum length of final text + * @param string $ellipsis String to append to the end of truncated text + * @param bool $adjustLength Subtract length of ellipsis from $length + * @param callable $measureLength Callable function used for determining the length of text + * @param callable $getSubstring Callable function used for getting the substrings + * + * @return string + */ + private function truncateInternal( + $string, $length, $ellipsis = '...', $adjustLength = true, $measureLength, $getSubstring + ) { + if ( !is_callable( $measureLength ) || !is_callable( $getSubstring ) ) { + throw new InvalidArgumentException( 'Invalid callback provided' ); + } + # Check if there is no need to truncate - if ( strlen( $string ) <= abs( $length ) ) { + if ( $measureLength( $string ) <= abs( $length ) ) { return $string; // no need to truncate } + # Use the localized ellipsis character if ( $ellipsis == '...' ) { $ellipsis = wfMessage( 'ellipsis' )->inLanguage( $this )->escaped(); @@ -3500,31 +3576,33 @@ class Language { if ( $length == 0 ) { return $ellipsis; // convention } + $stringOriginal = $string; # If ellipsis length is >= $length then we can't apply $adjustLength - if ( $adjustLength && strlen( $ellipsis ) >= abs( $length ) ) { + if ( $adjustLength && $measureLength( $ellipsis ) >= abs( $length ) ) { $string = $ellipsis; // this can be slightly unexpected # Otherwise, truncate and add ellipsis... } else { - $eLength = $adjustLength ? strlen( $ellipsis ) : 0; + $ellipsisLength = $adjustLength ? $measureLength( $ellipsis ) : 0; if ( $length > 0 ) { - $length -= $eLength; - $string = substr( $string, 0, $length ); // xyz... + $length -= $ellipsisLength; + $string = $getSubstring( $string, 0, $length ); // xyz... $string = $this->removeBadCharLast( $string ); $string = rtrim( $string ); $string = $string . $ellipsis; } else { - $length += $eLength; - $string = substr( $string, $length ); // ...xyz + $length += $ellipsisLength; + $string = $getSubstring( $string, $length ); // ...xyz $string = $this->removeBadCharFirst( $string ); $string = ltrim( $string ); $string = $ellipsis . $string; } } + # Do not truncate if the ellipsis makes the string longer/equal (T24181). # This check is *not* redundant if $adjustLength, due to the single case where # LEN($ellipsis) > ABS($limit arg); $stringOriginal could be shorter than $string. - if ( strlen( $string ) < strlen( $stringOriginal ) ) { + if ( $measureLength( $string ) < $measureLength( $stringOriginal ) ) { return $string; } else { return $stringOriginal; diff --git a/languages/data/Names.php b/languages/data/Names.php index 22398754d9..2252645c42 100644 --- a/languages/data/Names.php +++ b/languages/data/Names.php @@ -261,7 +261,7 @@ class Names { 'ky' => 'Кыргызча', # Kirghiz 'la' => 'Latina', # Latin 'lad' => 'Ladino', # Ladino - 'lb' => 'Lëtzebuergesch', # Luxemburguish + 'lb' => 'Lëtzebuergesch', # Luxembourgish 'lbe' => 'лакку', # Lak 'lez' => 'лезги', # Lezgi 'lfn' => 'Lingua Franca Nova', # Lingua Franca Nova diff --git a/maintenance/sql.php b/maintenance/sql.php index dd05bbe4ad..2c8bdb67ce 100644 --- a/maintenance/sql.php +++ b/maintenance/sql.php @@ -40,6 +40,7 @@ class MwSql extends Maintenance { 'Takes a file name containing SQL as argument or runs interactively.' ); $this->addOption( 'query', 'Run a single query instead of running interactively', false, true ); + $this->addOption( 'json', 'Output the results as JSON instead of PHP objects' ); $this->addOption( 'cluster', 'Use an external cluster by name', false, true ); $this->addOption( 'wikidb', 'The database wiki ID to use if not the current one', false, true ); @@ -175,9 +176,15 @@ class MwSql extends Maintenance { // Do nothing return; } elseif ( is_object( $res ) && $res->numRows() ) { + $out = ''; foreach ( $res as $row ) { - $this->output( print_r( $row, true ) ); + $out .= print_r( $row, true ); + $rows[] = $row; } + if ( $this->hasOption( 'json' ) ) { + $out = json_encode( $rows, JSON_PRETTY_PRINT ); + } + $this->output( $out . "\n" ); } else { $affected = $db->affectedRows(); $this->output( "Query OK, $affected row(s) affected\n" ); diff --git a/resources/src/startup.js b/resources/src/startup.js index fe0b02998b..cc313c7c74 100644 --- a/resources/src/startup.js +++ b/resources/src/startup.js @@ -30,7 +30,7 @@ window.mwNow = ( function () { * * Browsers we support in our modern run-time (Grade A): * - Chrome 13+ - * - IE 10+ + * - IE 11+ * - Firefox 4+ * - Safari 5+ * - Opera 15+ @@ -86,7 +86,7 @@ window.isCompatible = function ( str ) { // support in the modern run-time. // Note: Please extend the regex instead of adding new ones !( - ua.match( /webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$|googleweblight/ ) || + ua.match( /MSIE 10|webOS\/1\.[0-4]|SymbianOS|Series60|NetFront|Opera Mini|S40OviBrowser|MeeGo|Android.+Glass|^Mozilla\/5\.0 .+ Gecko\/$|googleweblight/ ) || ua.match( /PlayStation/i ) ) ); diff --git a/tests/phpunit/data/media/jpeg-xmp-loop.jpg b/tests/phpunit/data/media/jpeg-xmp-loop.jpg new file mode 100644 index 0000000000..962f3fe0e7 Binary files /dev/null and b/tests/phpunit/data/media/jpeg-xmp-loop.jpg differ diff --git a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php index 184d6260f8..ebf6e45199 100644 --- a/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php +++ b/tests/phpunit/includes/libs/rdbms/database/DatabaseSQLTest.php @@ -559,11 +559,11 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { 'uniqueIndexes' => [ 'field' ], 'rows' => [ 'field' => 'text', 'field2' => 'text2' ], ], - "DELETE FROM replace_table " . + "BEGIN; DELETE FROM replace_table " . "WHERE (field = 'text'); " . "INSERT INTO replace_table " . "(field,field2) " . - "VALUES ('text','text2')" + "VALUES ('text','text2'); COMMIT" ], [ [ @@ -575,11 +575,11 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { 'md_deps' => 'deps', ], ], - "DELETE FROM module_deps " . + "BEGIN; DELETE FROM module_deps " . "WHERE (md_module = 'module' AND md_skin = 'skin'); " . "INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps')" + "VALUES ('module','skin','deps'); COMMIT" ], [ [ @@ -597,7 +597,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { ], ], ], - "DELETE FROM module_deps " . + "BEGIN; DELETE FROM module_deps " . "WHERE (md_module = 'module' AND md_skin = 'skin'); " . "INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . @@ -606,7 +606,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { "WHERE (md_module = 'module2' AND md_skin = 'skin2'); " . "INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2')" + "VALUES ('module2','skin2','deps2'); COMMIT" ], [ [ @@ -624,7 +624,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { ], ], ], - "DELETE FROM module_deps " . + "BEGIN; DELETE FROM module_deps " . "WHERE (md_module = 'module') OR (md_skin = 'skin'); " . "INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . @@ -633,7 +633,7 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { "WHERE (md_module = 'module2') OR (md_skin = 'skin2'); " . "INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . - "VALUES ('module2','skin2','deps2')" + "VALUES ('module2','skin2','deps2'); COMMIT" ], [ [ @@ -645,9 +645,9 @@ class DatabaseSQLTest extends PHPUnit\Framework\TestCase { 'md_deps' => 'deps', ], ], - "INSERT INTO module_deps " . + "BEGIN; INSERT INTO module_deps " . "(md_module,md_skin,md_deps) " . - "VALUES ('module','skin','deps')" + "VALUES ('module','skin','deps'); COMMIT" ], ]; } diff --git a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php index 0991254119..97921727f5 100644 --- a/tests/phpunit/includes/media/JpegMetadataExtractorTest.php +++ b/tests/phpunit/includes/media/JpegMetadataExtractorTest.php @@ -108,4 +108,10 @@ class JpegMetadataExtractorTest extends MediaWikiTestCase { $expected = 'BE'; $this->assertEquals( $expected, $res['byteOrder'] ); } + + public function testInfiniteRead() { + // Should get past infinite loop and throw in wfUnpack() + $this->setExpectedException( 'MWException' ); + $res = JpegMetadataExtractor::segmentSplitter( $this->filePath . 'jpeg-xmp-loop.jpg' ); + } } diff --git a/tests/phpunit/languages/LanguageTest.php b/tests/phpunit/languages/LanguageTest.php index 5cb560299f..050ed83bed 100644 --- a/tests/phpunit/languages/LanguageTest.php +++ b/tests/phpunit/languages/LanguageTest.php @@ -209,70 +209,104 @@ class LanguageTest extends LanguageClassesTestCase { } /** - * @covers Language::truncate + * @covers Language::truncateForDatabase + * @covers Language::truncateInternal */ - public function testTruncate() { + public function testTruncateForDatabase() { $this->assertEquals( "XXX", - $this->getLang()->truncate( "1234567890", 0, 'XXX' ), + $this->getLang()->truncateForDatabase( "1234567890", 0, 'XXX' ), 'truncate prefix, len 0, small ellipsis' ); $this->assertEquals( "12345XXX", - $this->getLang()->truncate( "1234567890", 8, 'XXX' ), + $this->getLang()->truncateForDatabase( "1234567890", 8, 'XXX' ), 'truncate prefix, small ellipsis' ); $this->assertEquals( "123456789", - $this->getLang()->truncate( "123456789", 5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncateForDatabase( "123456789", 5, 'XXXXXXXXXXXXXXX' ), 'truncate prefix, large ellipsis' ); $this->assertEquals( "XXX67890", - $this->getLang()->truncate( "1234567890", -8, 'XXX' ), + $this->getLang()->truncateForDatabase( "1234567890", -8, 'XXX' ), 'truncate suffix, small ellipsis' ); $this->assertEquals( "123456789", - $this->getLang()->truncate( "123456789", -5, 'XXXXXXXXXXXXXXX' ), + $this->getLang()->truncateForDatabase( "123456789", -5, 'XXXXXXXXXXXXXXX' ), 'truncate suffix, large ellipsis' ); $this->assertEquals( "123XXX", - $this->getLang()->truncate( "123 ", 9, 'XXX' ), + $this->getLang()->truncateForDatabase( "123 ", 9, 'XXX' ), 'truncate prefix, with spaces' ); $this->assertEquals( "12345XXX", - $this->getLang()->truncate( "12345 8", 11, 'XXX' ), + $this->getLang()->truncateForDatabase( "12345 8", 11, 'XXX' ), 'truncate prefix, with spaces and non-space ending' ); $this->assertEquals( "XXX234", - $this->getLang()->truncate( "1 234", -8, 'XXX' ), + $this->getLang()->truncateForDatabase( "1 234", -8, 'XXX' ), 'truncate suffix, with spaces' ); $this->assertEquals( "12345XXX", - $this->getLang()->truncate( "1234567890", 5, 'XXX', false ), + $this->getLang()->truncateForDatabase( "1234567890", 5, 'XXX', false ), 'truncate without adjustment' ); $this->assertEquals( "泰乐菌...", - $this->getLang()->truncate( "泰乐菌素123456789", 11, '...', false ), + $this->getLang()->truncateForDatabase( "泰乐菌素123456789", 11, '...', false ), 'truncate does not chop Unicode characters in half' ); $this->assertEquals( "\n泰乐菌...", - $this->getLang()->truncate( "\n泰乐菌素123456789", 12, '...', false ), + $this->getLang()->truncateForDatabase( "\n泰乐菌素123456789", 12, '...', false ), 'truncate does not chop Unicode characters in half if there is a preceding newline' ); } + /** + * @dataProvider provideTruncateData + * @covers Language::truncateForVisual + * @covers Language::truncateInternal + */ + public function testTruncateForVisual( + $expected, $string, $length, $ellipsis = '...', $adjustLength = true + ) { + $this->assertEquals( + $expected, + $this->getLang()->truncateForVisual( $string, $length, $ellipsis, $adjustLength ) + ); + } + + /** + * @return array Format is ($expected, $string, $length, $ellipsis, $adjustLength) + */ + public static function provideTruncateData() { + return [ + [ "XXX", "тестирам да ли ради", 0, "XXX" ], + [ "testnXXX", "testni scenarij", 8, "XXX" ], + [ "حالة اختبار", "حالة اختبار", 5, "XXXXXXXXXXXXXXX" ], + [ "XXXедент", "прецедент", -8, "XXX" ], + [ "XXപിൾ", "ആപ്പിൾ", -5, "XX" ], + [ "神秘XXX", "神秘 ", 9, "XXX" ], + [ "ΔημιουργXXX", "Δημιουργία Σύμπαντος", 11, "XXX" ], + [ "XXXの家です", "地球は私たちの唯 の家です", -8, "XXX" ], + [ "زندگیXXX", "زندگی زیباست", 6, "XXX", false ], + [ "ცხოვრება...", "ცხოვრება არის საოცარი", 8, "...", false ], + [ "\nທ່ານ...", "\nທ່ານບໍ່ຮູ້ຫນັງສື", 5, "...", false ], + ]; + } + /** * @dataProvider provideHTMLTruncateData * @covers Language::truncateHTML diff --git a/tests/qunit/suites/resources/startup.test.js b/tests/qunit/suites/resources/startup.test.js index 0866b9ecee..6a704b5af8 100644 --- a/tests/qunit/suites/resources/startup.test.js +++ b/tests/qunit/suites/resources/startup.test.js @@ -21,11 +21,8 @@ 'Mozilla/5.0 (Windows NT 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.95 Safari/537.36 OPR/15.0.1147.153', 'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.57 Safari/537.36 OPR/16.0.1196.62', 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.125 Safari/537.36 OPR/23.0.1522.75', - // Internet Explorer 10+ - 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', + // Internet Explorer 11 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko', - // IE Mobile - 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 800)', // Edge 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246', // Edge Mobile @@ -107,6 +104,10 @@ blacklisted: [ /* Grade C */ + // Internet Explorer 10 + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0)', + // IE Mobile 10 + 'Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; HTC; Windows Phone 8X by HTC)', // PlayStation 'Mozilla/5.0 (PLAYSTATION 3; 1.10)', 'Mozilla/5.0 (PLAYSTATION 3; 3.55)', diff --git a/tests/selenium/README.md b/tests/selenium/README.md index 16283772a0..2dbf27140a 100644 --- a/tests/selenium/README.md +++ b/tests/selenium/README.md @@ -37,14 +37,14 @@ To run only one test (name contains string 'preferences'): The runner reads the config file `wdio.conf.js` and runs the spec listed in `page.js`. -The defaults in the configuration files aim are targetting a MediaWiki-Vagrant -installation on installation on http://127.0.0.1:8080 with a user Admin and -password 'vagrant'. Those settings can be overriden using environment +The defaults in the configuration files aim are targeting a MediaWiki-Vagrant +installation on http://127.0.0.1:8080 with a user Admin and +password 'vagrant'. Those settings can be overridden using environment variables: `MW_SERVER`: to be set to the value of your $wgServer -`MW_SCRIPT_PATH`: ditto with $wgScriptPath -`MEDIAWIKI_USER`: username of an account that can create users on the wiki. +`MW_SCRIPT_PATH`: ditto with $wgScriptPath +`MEDIAWIKI_USER`: username of an account that can create users on the wiki `MEDIAWIKI_PASSWORD`: password for above user Example: