From: jenkins-bot Date: Mon, 25 Mar 2019 14:45:20 +0000 (+0000) Subject: Merge "DefaultSettings: Remove deprecated $wgDBmysql5 from core" X-Git-Tag: 1.34.0-rc.0~2378 X-Git-Url: http://git.cyclocoop.org/%7B%24www_url%7Dadmin/compta/operations/recherche.php?a=commitdiff_plain;h=e73b5913ea7a82bb4fcd319653e98990f1e11999;hp=26118ad09496b5930ab51925593afed7160bf318;p=lhc%2Fweb%2Fwiklou.git Merge "DefaultSettings: Remove deprecated $wgDBmysql5 from core" --- diff --git a/.phan/config.php b/.phan/config.php new file mode 100644 index 0000000000..e4ba47f265 --- /dev/null +++ b/.phan/config.php @@ -0,0 +1,197 @@ + '.phan/internal_stubs/memcached.phan_php', + 'oci8' => '.phan/internal_stubs/oci8.phan_php', + 'sqlsrv' => '.phan/internal_stubs/sqlsrv.phan_php', + 'tideways' => '.phan/internal_stubs/tideways.phan_php', +]; + +$cfg['directory_list'] = [ + 'includes/', + 'languages/', + 'maintenance/', + 'mw-config/', + 'resources/', + 'vendor/', + '.phan/stubs/', +]; + +$cfg['exclude_analysis_directory_list'] = [ + 'vendor/', + '.phan/stubs/', + // The referenced classes are not available in vendor, only when + // included from composer. + 'includes/composer/', + // Directly references classes that only exist in Translate extension + 'maintenance/language/', + // External class + 'includes/libs/jsminplus.php', +]; + +$cfg['suppress_issue_types'] = array_merge( $cfg['suppress_issue_types'], [ + // approximate error count: 18 + "PhanAccessMethodInternal", + // approximate error count: 17 + "PhanCommentParamOnEmptyParamList", + // approximate error count: 30 + "PhanCommentParamWithoutRealParam", + // approximate error count: 2 + "PhanCompatibleNegativeStringOffset", + // approximate error count: 1 + "PhanEmptyFQSENInCallable", + // approximate error count: 1 + "PhanInvalidCommentForDeclarationType", + // approximate error count: 6 + "PhanNonClassMethodCall", + // approximate error count: 21 + "PhanParamReqAfterOpt", + // approximate error count: 27 + "PhanParamSignatureMismatch", + // approximate error count: 4 + "PhanParamSignatureMismatchInternal", + // approximate error count: 1 + "PhanParamSignatureRealMismatchTooFewParameters", + // approximate error count: 1 + "PhanParamSuspiciousOrder", + // approximate error count: 127 + "PhanParamTooMany", + // approximate error count: 2 + "PhanParamTooManyCallable", + // approximate error count: 1 + "PhanParamTooManyInternal", + // approximate error count: 2 + "PhanPluginDuplicateExpressionBinaryOp", + // approximate error count: 2 + "PhanTraitParentReference", + // approximate error count: 27 + "PhanTypeArraySuspicious", + // approximate error count: 33 + "PhanTypeArraySuspiciousNullable", + // approximate error count: 26 + "PhanTypeComparisonFromArray", + // approximate error count: 2 + "PhanTypeComparisonToArray", + // approximate error count: 1 + "PhanTypeConversionFromArray", + // approximate error count: 2 + "PhanTypeExpectedObjectOrClassName", + // approximate error count: 7 + "PhanTypeExpectedObjectPropAccess", + // approximate error count: 3 + "PhanTypeInstantiateAbstract", + // approximate error count: 1 + "PhanTypeInvalidCallableArraySize", + // approximate error count: 62 + "PhanTypeInvalidDimOffset", + // approximate error count: 10 + "PhanTypeInvalidExpressionArrayDestructuring", + // approximate error count: 1 + "PhanTypeInvalidLeftOperand", + // approximate error count: 7 + "PhanTypeInvalidLeftOperandOfIntegerOp", + // approximate error count: 2 + "PhanTypeInvalidRightOperand", + // approximate error count: 2 + "PhanTypeInvalidRightOperandOfIntegerOp", + // approximate error count: 1 + "PhanTypeMagicVoidWithReturn", + // approximate error count: 152 + "PhanTypeMismatchArgument", + // approximate error count: 28 + "PhanTypeMismatchArgumentInternal", + // approximate error count: 1 + "PhanTypeMismatchBitwiseBinaryOperands", + // approximate error count: 1 + "PhanTypeMismatchDeclaredParam", + // approximate error count: 2 + "PhanTypeMismatchDimEmpty", + // approximate error count: 29 + "PhanTypeMismatchDimFetch", + // approximate error count: 10 + "PhanTypeMismatchForeach", + // approximate error count: 77 + "PhanTypeMismatchProperty", + // approximate error count: 88 + "PhanTypeMismatchReturn", + // approximate error count: 43 + "PhanTypeMissingReturn", + // approximate error count: 1 + "PhanTypeNoAccessiblePropertiesForeach", + // approximate error count: 4 + "PhanTypeNonVarPassByRef", + // approximate error count: 12 + "PhanTypeObjectUnsetDeclaredProperty", + // approximate error count: 9 + "PhanTypeSuspiciousNonTraversableForeach", + // approximate error count: 3 + "PhanTypeSuspiciousStringExpression", + // approximate error count: 22 + "PhanUndeclaredConstant", + // approximate error count: 3 + "PhanUndeclaredInvokeInCallable", + // approximate error count: 242 + "PhanUndeclaredMethod", + // approximate error count: 847 + "PhanUndeclaredProperty", + // approximate error count: 1 + "PhanUndeclaredTypeReturnType", + // approximate error count: 3 + "PhanUndeclaredTypeThrowsType", + // approximate error count: 2 + "PhanUndeclaredVariableAssignOp", + // approximate error count: 55 + "PhanUndeclaredVariableDim", + // approximate error count: 4 + "PhanUnextractableAnnotationElementName", + // approximate error count: 4 + "PhanUnextractableAnnotationSuffix", +] ); + +$cfg['ignore_undeclared_variables_in_global_scope'] = true; +$cfg['globals_type_map']['IP'] = 'string'; + +return $cfg; diff --git a/.phan/internal_stubs/memcached.phan_php b/.phan/internal_stubs/memcached.phan_php new file mode 100644 index 0000000000..8a85bafe39 --- /dev/null +++ b/.phan/internal_stubs/memcached.phan_php @@ -0,0 +1,180 @@ + */includes/Feed\.php - */includes/RevisionList\.php */includes/installer/PhpBugTests\.php */includes/specials/SpecialMostinterwikis\.php */includes/compat/XMPReader\.php @@ -232,7 +231,6 @@ */includes/poolcounter/PoolCounter\.php */includes/PrefixSearch\.php */includes/profiler/SectionProfiler\.php - */includes/RevisionList\.php */includes/search/SearchEngine\.php */includes/specialpage/LoginSignupSpecialPage\.php */includes/specials/forms/PreferencesFormLegacy\.php diff --git a/autoload.php b/autoload.php index 4172ed3d5a..bbfe251283 100644 --- a/autoload.php +++ b/autoload.php @@ -426,6 +426,7 @@ $wgAutoloadLocalClasses = [ 'DumpFilter' => __DIR__ . '/includes/export/DumpFilter.php', 'DumpGZipOutput' => __DIR__ . '/includes/export/DumpGZipOutput.php', 'DumpIterator' => __DIR__ . '/maintenance/dumpIterator.php', + 'DumpLBZip2Output' => __DIR__ . '/includes/export/DumpLBZip2Output.php', 'DumpLatestFilter' => __DIR__ . '/includes/export/DumpLatestFilter.php', 'DumpLinks' => __DIR__ . '/maintenance/dumpLinks.php', 'DumpMessages' => __DIR__ . '/maintenance/language/dumpMessages.php', @@ -1278,10 +1279,10 @@ $wgAutoloadLocalClasses = [ 'Revision' => __DIR__ . '/includes/Revision.php', 'RevisionDeleteUser' => __DIR__ . '/includes/revisiondelete/RevisionDeleteUser.php', 'RevisionDeleter' => __DIR__ . '/includes/revisiondelete/RevisionDeleter.php', - 'RevisionItem' => __DIR__ . '/includes/RevisionList.php', - 'RevisionItemBase' => __DIR__ . '/includes/RevisionList.php', - 'RevisionList' => __DIR__ . '/includes/RevisionList.php', - 'RevisionListBase' => __DIR__ . '/includes/RevisionList.php', + 'RevisionItem' => __DIR__ . '/includes/revisionlist/RevisionItem.php', + 'RevisionItemBase' => __DIR__ . '/includes/revisionlist/RevisionItemBase.php', + 'RevisionList' => __DIR__ . '/includes/revisionlist/RevisionList.php', + 'RevisionListBase' => __DIR__ . '/includes/revisionlist/RevisionListBase.php', 'RiffExtractor' => __DIR__ . '/includes/libs/RiffExtractor.php', 'RightsLogFormatter' => __DIR__ . '/includes/logging/RightsLogFormatter.php', 'RollbackAction' => __DIR__ . '/includes/actions/RollbackAction.php', diff --git a/composer.json b/composer.json index 1fe79cd8c8..e80eb3415b 100644 --- a/composer.json +++ b/composer.json @@ -64,7 +64,6 @@ "hamcrest/hamcrest-php": "^2.0", "jakub-onderka/php-console-highlighter": "0.3.2", "jakub-onderka/php-parallel-lint": "0.9.2", - "jetbrains/phpstorm-stubs": "dev-master#38ff1a581b297f7901e961b8c923862ea80c3b96", "justinrainbow/json-schema": "~5.2", "mediawiki/mediawiki-codesniffer": "24.0.0", "monolog/monolog": "~1.22.1", @@ -76,7 +75,7 @@ "wikimedia/avro": "1.8.0", "wikimedia/testing-access-wrapper": "~1.0", "wmde/hamcrest-html-matchers": "^0.1.0", - "mediawiki/mediawiki-phan-config": "0.3.0" + "mediawiki/mediawiki-phan-config": "0.5.0" }, "replace": { "symfony/polyfill-ctype": "1.99", diff --git a/docs/hooks.txt b/docs/hooks.txt index 4ef680ab0c..139123d5cf 100644 --- a/docs/hooks.txt +++ b/docs/hooks.txt @@ -2446,10 +2446,14 @@ $userLang: the user language (Language or StubUserLang object) $wikiPage: the WikiPage (object) being saved $user: the user (object) saving the article $content: the new article content, as a Content object -$summary: the article summary (comment) -$isminor: minor flag -$iswatch: watch flag -$section: section # +&$summary: CommentStoreComment object containing the edit comment. Can be replaced with a new one. +$isminor: Boolean flag specifying if the edit was marked as minor. +$iswatch: Previously a watch flag. Currently unused, always null. +$section: Previously the section number being edited. Currently unused, always null. +$flags: All EDIT_… flags (including EDIT_MINOR) as an integer number. See WikiPage::doEditContent + documentation for flags' definition. +$status: StatusValue object for the hook handlers resulting status. Either set $status->fatal() or + return false to abort the save action. 'PageContentSaveComplete': After an article has been updated. $wikiPage: WikiPage modified diff --git a/includes/ActorMigration.php b/includes/ActorMigration.php index 0c33eb9bfc..597b8e76e7 100644 --- a/includes/ActorMigration.php +++ b/includes/ActorMigration.php @@ -136,11 +136,7 @@ class ActorMigration { * @return string[] [ $text, $actor ] */ private static function getFieldNames( $key ) { - if ( isset( self::$specialFields[$key] ) ) { - return self::$specialFields[$key]; - } - - return [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ]; + return self::$specialFields[$key] ?? [ $key . '_text', substr( $key, 0, -5 ) . '_actor' ]; } /** diff --git a/includes/Linker.php b/includes/Linker.php index 5e07f1e1de..decc13cb41 100644 --- a/includes/Linker.php +++ b/includes/Linker.php @@ -1710,12 +1710,8 @@ class Linker { static function splitTrail( $trail ) { $regex = MediaWikiServices::getInstance()->getContentLanguage()->linkTrail(); $inside = ''; - if ( $trail !== '' ) { - $m = []; - if ( preg_match( $regex, $trail, $m ) ) { - $inside = $m[1]; - $trail = $m[2]; - } + if ( $trail !== '' && preg_match( $regex, $trail, $m ) ) { + list( , $inside, $trail ) = $m; } return [ $inside, $trail ]; } diff --git a/includes/MagicWordArray.php b/includes/MagicWordArray.php index fde32ce4fd..707c644a8d 100644 --- a/includes/MagicWordArray.php +++ b/includes/MagicWordArray.php @@ -268,10 +268,7 @@ class MagicWordArray { return $hash[1][$text]; } $lc = $this->factory->getContentLanguage()->lc( $text ); - if ( isset( $hash[0][$lc] ) ) { - return $hash[0][$lc]; - } - return false; + return $hash[0][$lc] ?? false; } /** diff --git a/includes/PHPVersionCheck.php b/includes/PHPVersionCheck.php index cbe63a33d4..01d5f9dac3 100644 --- a/includes/PHPVersionCheck.php +++ b/includes/PHPVersionCheck.php @@ -20,6 +20,7 @@ // phpcs:disable Generic.Arrays.DisallowLongArraySyntax,PSR2.Classes.PropertyDeclaration,MediaWiki.Usage.DirUsage // phpcs:disable Squiz.Scope.MemberVarScope.Missing,Squiz.Scope.MethodScope.Missing +// @phan-file-suppress PhanPluginDuplicateConditionalNullCoalescing /** * Check PHP Version, as well as for composer dependencies in entry points, * and display something vaguely comprehensible in the event of a totally diff --git a/includes/RevisionList.php b/includes/RevisionList.php deleted file mode 100644 index 5243cc65dd..0000000000 --- a/includes/RevisionList.php +++ /dev/null @@ -1,447 +0,0 @@ -setContext( $context ); - $this->title = $title; - } - - /** - * Select items only where the ID is any of the specified values - * @param array $ids - */ - function filterByIds( array $ids ) { - $this->ids = $ids; - } - - /** - * Get the internal type name of this list. Equal to the table name. - * Override this function. - * @return null - */ - public function getType() { - return null; - } - - /** - * Initialise the current iteration pointer - */ - protected function initCurrent() { - $row = $this->res->current(); - if ( $row ) { - $this->current = $this->newItem( $row ); - } else { - $this->current = false; - } - } - - /** - * Start iteration. This must be called before current() or next(). - * @return Revision First list item - */ - public function reset() { - if ( !$this->res ) { - $this->res = $this->doQuery( wfGetDB( DB_REPLICA ) ); - } else { - $this->res->rewind(); - } - $this->initCurrent(); - return $this->current; - } - - public function rewind() { - $this->reset(); - } - - /** - * Get the current list item, or false if we are at the end - * @return Revision - */ - public function current() { - return $this->current; - } - - /** - * Move the iteration pointer to the next list item, and return it. - * @return Revision - */ - public function next() { - $this->res->next(); - $this->initCurrent(); - return $this->current; - } - - public function key() { - return $this->res ? $this->res->key() : 0; - } - - public function valid() { - return $this->res ? $this->res->valid() : false; - } - - /** - * Get the number of items in the list. - * @return int - */ - public function length() { - if ( !$this->res ) { - return 0; - } else { - return $this->res->numRows(); - } - } - - /** - * Do the DB query to iterate through the objects. - * @param IDatabase $db DB object to use for the query - */ - abstract public function doQuery( $db ); - - /** - * Create an item object from a DB result row - * @param object $row - */ - abstract public function newItem( $row ); -} - -/** - * Abstract base class for revision items - */ -abstract class RevisionItemBase { - /** @var RevisionListBase The parent */ - protected $list; - - /** The database result row */ - protected $row; - - /** - * @param RevisionListBase $list - * @param object $row DB result row - */ - public function __construct( $list, $row ) { - $this->list = $list; - $this->row = $row; - } - - /** - * Get the DB field name associated with the ID list. - * Override this function. - * @return null - */ - public function getIdField() { - return null; - } - - /** - * Get the DB field name storing timestamps. - * Override this function. - * @return bool - */ - public function getTimestampField() { - return false; - } - - /** - * Get the DB field name storing user ids. - * Override this function. - * @return bool - */ - public function getAuthorIdField() { - return false; - } - - /** - * Get the DB field name storing user names. - * Override this function. - * @return bool - */ - public function getAuthorNameField() { - return false; - } - - /** - * Get the DB field name storing actor ids. - * Override this function. - * @since 1.31 - * @return bool - */ - public function getAuthorActorField() { - return false; - } - - /** - * Get the ID, as it would appear in the ids URL parameter - * @return int - */ - public function getId() { - $field = $this->getIdField(); - return $this->row->$field; - } - - /** - * Get the date, formatted in user's language - * @return string - */ - public function formatDate() { - return $this->list->getLanguage()->userDate( $this->getTimestamp(), - $this->list->getUser() ); - } - - /** - * Get the time, formatted in user's language - * @return string - */ - public function formatTime() { - return $this->list->getLanguage()->userTime( $this->getTimestamp(), - $this->list->getUser() ); - } - - /** - * Get the timestamp in MW 14-char form - * @return mixed - */ - public function getTimestamp() { - $field = $this->getTimestampField(); - return wfTimestamp( TS_MW, $this->row->$field ); - } - - /** - * Get the author user ID - * @return int - */ - public function getAuthorId() { - $field = $this->getAuthorIdField(); - return intval( $this->row->$field ); - } - - /** - * Get the author user name - * @return string - */ - public function getAuthorName() { - $field = $this->getAuthorNameField(); - return strval( $this->row->$field ); - } - - /** - * Get the author actor ID - * @since 1.31 - * @return string - */ - public function getAuthorActor() { - $field = $this->getAuthorActorField(); - return strval( $this->row->$field ); - } - - /** - * Returns true if the current user can view the item - */ - abstract public function canView(); - - /** - * Returns true if the current user can view the item text/file - */ - abstract public function canViewContent(); - - /** - * Get the HTML of the list item. Should be include "
  • " tags. - * This is used to show the list in HTML form, by the special page. - */ - abstract public function getHTML(); - - /** - * Returns an instance of LinkRenderer - * @return \MediaWiki\Linker\LinkRenderer - */ - protected function getLinkRenderer() { - return MediaWikiServices::getInstance()->getLinkRenderer(); - } -} - -class RevisionList extends RevisionListBase { - public function getType() { - return 'revision'; - } - - /** - * @param IDatabase $db - * @return mixed - */ - public function doQuery( $db ) { - $conds = [ 'rev_page' => $this->title->getArticleID() ]; - if ( $this->ids !== null ) { - $conds['rev_id'] = array_map( 'intval', $this->ids ); - } - $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] ); - return $db->select( - $revQuery['tables'], - $revQuery['fields'], - $conds, - __METHOD__, - [ 'ORDER BY' => 'rev_id DESC' ], - $revQuery['joins'] - ); - } - - public function newItem( $row ) { - return new RevisionItem( $this, $row ); - } -} - -/** - * Item class for a live revision table row - */ -class RevisionItem extends RevisionItemBase { - /** @var Revision */ - protected $revision; - - /** @var RequestContext */ - protected $context; - - public function __construct( $list, $row ) { - parent::__construct( $list, $row ); - $this->revision = new Revision( $row ); - $this->context = $list->getContext(); - } - - public function getIdField() { - return 'rev_id'; - } - - public function getTimestampField() { - return 'rev_timestamp'; - } - - public function getAuthorIdField() { - return 'rev_user'; - } - - public function getAuthorNameField() { - return 'rev_user_text'; - } - - public function canView() { - return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() ); - } - - public function canViewContent() { - return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() ); - } - - public function isDeleted() { - return $this->revision->isDeleted( Revision::DELETED_TEXT ); - } - - /** - * Get the HTML link to the revision text. - * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - protected function getRevisionLink() { - $date = $this->list->getLanguage()->userTimeAndDate( - $this->revision->getTimestamp(), $this->list->getUser() ); - - if ( $this->isDeleted() && !$this->canViewContent() ) { - return htmlspecialchars( $date ); - } - $linkRenderer = $this->getLinkRenderer(); - return $linkRenderer->makeKnownLink( - $this->list->title, - $date, - [], - [ - 'oldid' => $this->revision->getId(), - 'unhide' => 1 - ] - ); - } - - /** - * Get the HTML link to the diff. - * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - protected function getDiffLink() { - if ( $this->isDeleted() && !$this->canViewContent() ) { - return $this->context->msg( 'diff' )->escaped(); - } else { - $linkRenderer = $this->getLinkRenderer(); - return $linkRenderer->makeKnownLink( - $this->list->title, - $this->list->msg( 'diff' )->text(), - [], - [ - 'diff' => $this->revision->getId(), - 'oldid' => 'prev', - 'unhide' => 1 - ] - ); - } - } - - /** - * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class - * should inherit from this one, and implement an appropriate interface instead - * of extending RevDelItem - * @return string - */ - public function getHTML() { - $difflink = $this->context->msg( 'parentheses' ) - ->rawParams( $this->getDiffLink() )->escaped(); - $revlink = $this->getRevisionLink(); - $userlink = Linker::revUserLink( $this->revision ); - $comment = Linker::revComment( $this->revision ); - if ( $this->isDeleted() ) { - $revlink = "$revlink"; - } - return "
  • $difflink $revlink $userlink $comment
  • "; - } -} diff --git a/includes/api/ApiFormatBase.php b/includes/api/ApiFormatBase.php index e03352554b..bff9fd0095 100644 --- a/includes/api/ApiFormatBase.php +++ b/includes/api/ApiFormatBase.php @@ -158,11 +158,9 @@ abstract class ApiFormatBase extends ApiBase { if ( !is_array( $paramSettings ) ) { return $paramSettings; - } elseif ( isset( $paramSettings[self::PARAM_DFLT] ) ) { - return $paramSettings[self::PARAM_DFLT]; - } else { - return null; } + + return $paramSettings[self::PARAM_DFLT] ?? null; } /** diff --git a/includes/api/ApiResult.php b/includes/api/ApiResult.php index c4a31c7845..c27b10e344 100644 --- a/includes/api/ApiResult.php +++ b/includes/api/ApiResult.php @@ -498,7 +498,7 @@ class ApiResult implements ApiSerializable { throw new InvalidArgumentException( 'Content value must be named' ); } $this->addContentField( $path, $name, $flags ); - $this->addValue( $path, $name, $value, $flags ); + return $this->addValue( $path, $name, $value, $flags ); } /** diff --git a/includes/block/BlockRestriction.php b/includes/block/BlockRestriction.php index ca36cdafa1..b92cda80f5 100644 --- a/includes/block/BlockRestriction.php +++ b/includes/block/BlockRestriction.php @@ -73,7 +73,7 @@ class BlockRestriction { * @return bool */ public static function insert( array $restrictions ) { - if ( empty( $restrictions ) ) { + if ( !$restrictions ) { return false; } @@ -85,7 +85,7 @@ class BlockRestriction { $rows[] = $restriction->toRow(); } - if ( empty( $rows ) ) { + if ( !$rows ) { return false; } diff --git a/includes/cache/localisation/LCStoreStaticArray.php b/includes/cache/localisation/LCStoreStaticArray.php index 75c8465abf..f860146804 100644 --- a/includes/cache/localisation/LCStoreStaticArray.php +++ b/includes/cache/localisation/LCStoreStaticArray.php @@ -101,8 +101,7 @@ class LCStoreStaticArray implements LCStore { return $encoded; } - $type = $encoded[0]; - $data = $encoded[1]; + list( $type, $data ) = $encoded; switch ( $type ) { case 'a': diff --git a/includes/cache/localisation/LocalisationCache.php b/includes/cache/localisation/LocalisationCache.php index 1d00d19a51..8df80138d0 100644 --- a/includes/cache/localisation/LocalisationCache.php +++ b/includes/cache/localisation/LocalisationCache.php @@ -536,7 +536,6 @@ class LocalisationCache { } } elseif ( $_fileType == 'aliases' ) { if ( isset( $aliases ) ) { - /** @suppress PhanUndeclaredVariable */ $data['aliases'] = $aliases; } } else { diff --git a/includes/config/ConfigRepository.php b/includes/config/ConfigRepository.php index 96dc51c118..2874c334f4 100644 --- a/includes/config/ConfigRepository.php +++ b/includes/config/ConfigRepository.php @@ -71,10 +71,8 @@ class ConfigRepository implements SalvageableService { if ( !$this->has( $name, true ) ) { throw new \ConfigException( 'The configuration option ' . $name . ' does not exist.' ); } - if ( isset( $this->configItems['public'][$name] ) ) { - return $this->configItems['public'][$name]; - } - return $this->configItems['private'][$name]; + + return $this->configItems['public'][$name] ?? $this->configItems['private'][$name]; } /** diff --git a/includes/db/DatabaseOracle.php b/includes/db/DatabaseOracle.php index 16bde4b959..bc3873d26e 100644 --- a/includes/db/DatabaseOracle.php +++ b/includes/db/DatabaseOracle.php @@ -700,14 +700,6 @@ class DatabaseOracle extends Database { return new Blob( $b ); } - function decodeBlob( $b ) { - if ( $b instanceof Blob ) { - $b = $b->fetch(); - } - - return $b; - } - function unionQueries( $sqls, $all ) { $glue = ' UNION ALL '; @@ -1350,10 +1342,6 @@ class DatabaseOracle extends Database { return 'BITOR(' . $fieldLeft . ', ' . $fieldRight . ')'; } - function getServer() { - return $this->server; - } - public function buildGroupConcatField( $delim, $table, $field, $conds = '', $join_conds = [] ) { diff --git a/includes/db/MWLBFactory.php b/includes/db/MWLBFactory.php index 51eb216e5d..9851460bff 100644 --- a/includes/db/MWLBFactory.php +++ b/includes/db/MWLBFactory.php @@ -109,7 +109,7 @@ abstract class MWLBFactory { } $ldTP = $mainConfig->get( 'DBprefix' ); // local domain prefix - $srvTP = $server['tablePrefix'] ?? null; // server table prefix + $srvTP = $server['tablePrefix'] ?? ''; // server table prefix if ( $srvTP !== '' && $srvTP !== $ldTP ) { self::reportMismatchedPrefixes( $srvTP, $ldTP ); } diff --git a/includes/diff/DiffEngine.php b/includes/diff/DiffEngine.php index edf844417e..546a12cb79 100644 --- a/includes/diff/DiffEngine.php +++ b/includes/diff/DiffEngine.php @@ -456,9 +456,7 @@ class DiffEngine { // need to store these so we don't lose them when they're // overwritten by the recursion - $len = $snake[2]; - $startx = $snake[0]; - $starty = $snake[1]; + list( $startx, $starty, $len ) = $snake; // the middle snake is part of the LCS, store it for ( $i = 0; $i < $len; ++$i ) { diff --git a/includes/export/DumpLBZip2Output.php b/includes/export/DumpLBZip2Output.php new file mode 100644 index 0000000000..b923995c8d --- /dev/null +++ b/includes/export/DumpLBZip2Output.php @@ -0,0 +1,39 @@ + + * Copyright © 2019 Wikimedia Foundation Inc. + * https://www.mediawiki.org/ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + */ + +/** + * @ingroup Dump + * @since 1.33 + */ +class DumpLBZip2Output extends DumpPipeOutput { + /** + * @param string $file + */ + function __construct( $file ) { + # use only one core + parent::__construct( "lbzip2 -n 1", $file ); + } +} diff --git a/includes/filerepo/file/ForeignAPIFile.php b/includes/filerepo/file/ForeignAPIFile.php index c49810cfeb..3a75720cdf 100644 --- a/includes/filerepo/file/ForeignAPIFile.php +++ b/includes/filerepo/file/ForeignAPIFile.php @@ -184,11 +184,7 @@ class ForeignAPIFile extends File { * null on error */ public function getExtendedMetadata() { - if ( isset( $this->mInfo['extmetadata'] ) ) { - return $this->mInfo['extmetadata']; - } - - return null; + return $this->mInfo['extmetadata'] ?? null; } /** diff --git a/includes/gallery/TraditionalImageGallery.php b/includes/gallery/TraditionalImageGallery.php index 5ede631aa3..9de7eb8408 100644 --- a/includes/gallery/TraditionalImageGallery.php +++ b/includes/gallery/TraditionalImageGallery.php @@ -72,11 +72,9 @@ class TraditionalImageGallery extends ImageGalleryBase { $lang = $this->getRenderLang(); # Output each image... foreach ( $this->mImages as $pair ) { + // "text" means "caption" here /** @var Title $nt */ - $nt = $pair[0]; - $text = $pair[1]; # "text" means "caption" here - $alt = $pair[2]; - $link = $pair[3]; + list( $nt, $text, $alt, $link ) = $pair; $descQuery = false; if ( $nt->getNamespace() === NS_FILE ) { diff --git a/includes/installer/DatabaseUpdater.php b/includes/installer/DatabaseUpdater.php index 7a92807e0c..750f10870a 100644 --- a/includes/installer/DatabaseUpdater.php +++ b/includes/installer/DatabaseUpdater.php @@ -410,9 +410,7 @@ abstract class DatabaseUpdater { $this->updatesSkipped = []; foreach ( $updates as $funcList ) { - $func = $funcList[0]; - $args = $funcList[1]; - $origParams = $funcList[2]; + list( $func, $args, $origParams ) = $funcList; $func( ...$args ); flush(); $this->updatesSkipped[] = $origParams; diff --git a/includes/installer/Installer.php b/includes/installer/Installer.php index 0bc0a8362f..a954008516 100644 --- a/includes/installer/Installer.php +++ b/includes/installer/Installer.php @@ -1501,7 +1501,7 @@ abstract class Installer { $data = $registry->readFromQueue( $queue ); $wgAutoloadClasses += $data['autoload']; - /** @suppress PhanUndeclaredVariable $wgHooks is set by DefaultSettings */ + // @phan-suppress-next-line PhanUndeclaredVariable $wgHooks is set by DefaultSettings $hooksWeWant = $wgHooks['LoadExtensionSchemaUpdates'] ?? []; if ( isset( $data['globals']['wgHooks']['LoadExtensionSchemaUpdates'] ) ) { diff --git a/includes/libs/filebackend/FileBackend.php b/includes/libs/filebackend/FileBackend.php index 19373eaef9..7bc3045c49 100644 --- a/includes/libs/filebackend/FileBackend.php +++ b/includes/libs/filebackend/FileBackend.php @@ -162,9 +162,8 @@ abstract class FileBackend implements LoggerAwareInterface { */ public function __construct( array $config ) { $this->name = $config['name']; - $this->domainId = isset( $config['domainId'] ) - ? $config['domainId'] // e.g. "my_wiki-en_" - : $config['wikiId']; // b/c alias + $this->domainId = $config['domainId'] // e.g. "my_wiki-en_" + ?? $config['wikiId']; // b/c alias if ( !preg_match( '!^[a-zA-Z0-9-_]{1,255}$!', $this->name ) ) { throw new InvalidArgumentException( "Backend name '{$this->name}' is invalid." ); } elseif ( !is_string( $this->domainId ) ) { diff --git a/includes/libs/filebackend/FileBackendMultiWrite.php b/includes/libs/filebackend/FileBackendMultiWrite.php index 655a71048c..9524155d6b 100644 --- a/includes/libs/filebackend/FileBackendMultiWrite.php +++ b/includes/libs/filebackend/FileBackendMultiWrite.php @@ -87,9 +87,6 @@ class FileBackendMultiWrite extends FileBackend { * This will apply such updates post-send for web requests. Note that * any checks from "syncChecks" are still synchronous. * - * Bogus warning - * @suppress PhanAccessMethodProtected - * * @param array $config * @throws FileBackendError */ diff --git a/includes/libs/objectcache/BagOStuff.php b/includes/libs/objectcache/BagOStuff.php index e2b021293a..4fe64f2641 100644 --- a/includes/libs/objectcache/BagOStuff.php +++ b/includes/libs/objectcache/BagOStuff.php @@ -278,7 +278,7 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { * @throws InvalidArgumentException */ public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - return $this->mergeViaLock( $key, $callback, $exptime, $attempts, $flags ); + return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); } /** @@ -311,11 +311,13 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { // Derive the new value from the old value $value = call_user_func( $callback, $this, $key, $currentValue, $exptime ); + $hadNoCurrentValue = ( $currentValue === false ); + unset( $currentValue ); // free RAM in case the value is large $this->clearLastError(); if ( $value === false ) { $success = true; // do nothing - } elseif ( $currentValue === false ) { + } elseif ( $hadNoCurrentValue ) { // Try to create the key, failing if it gets created in the meantime $success = $this->add( $key, $value, $exptime, $flags ); } else { @@ -369,58 +371,6 @@ abstract class BagOStuff implements IExpiringStore, LoggerAwareInterface { return $success; } - /** - * @see BagOStuff::merge() - * - * @param string $key - * @param callable $callback Callback method to be executed - * @param int $exptime Either an interval in seconds or a unix timestamp for expiry - * @param int $attempts The amount of times to attempt a merge in case of failure - * @param int $flags Bitfield of BagOStuff::WRITE_* constants - * @return bool Success - */ - protected function mergeViaLock( $key, $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { - if ( $attempts <= 1 ) { - $timeout = 0; // clearly intended to be "non-blocking" - } else { - $timeout = 3; - } - - if ( !$this->lock( $key, $timeout ) ) { - return false; - } - - $this->clearLastError(); - $reportDupes = $this->reportDupes; - $this->reportDupes = false; - $currentValue = $this->get( $key, self::READ_LATEST ); - $this->reportDupes = $reportDupes; - - if ( $this->getLastError() ) { - $this->logger->warning( - __METHOD__ . ' failed due to I/O error on get() for {key}.', - [ 'key' => $key ] - ); - - $success = false; - } else { - // Derive the new value from the old value - $value = call_user_func( $callback, $this, $key, $currentValue, $exptime ); - if ( $value === false ) { - $success = true; // do nothing - } else { - $success = $this->set( $key, $value, $exptime, $flags ); // set the new value - } - } - - if ( !$this->unlock( $key ) ) { - // this should never happen - trigger_error( "Could not release lock for key '$key'." ); - } - - return $success; - } - /** * Change the expiration on a key if it exists * diff --git a/includes/libs/objectcache/MultiWriteBagOStuff.php b/includes/libs/objectcache/MultiWriteBagOStuff.php index 5cf9de4cc8..2d3eed583a 100644 --- a/includes/libs/objectcache/MultiWriteBagOStuff.php +++ b/includes/libs/objectcache/MultiWriteBagOStuff.php @@ -104,7 +104,7 @@ class MultiWriteBagOStuff extends BagOStuff { if ( ( $flags & self::READ_LATEST ) == self::READ_LATEST ) { // If the latest write was a delete(), we do NOT want to fallback // to the other tiers and possibly see the old value. Also, this - // is used by mergeViaLock(), which only needs to hit the primary. + // is used by merge(), which only needs to hit the primary. return $this->caches[0]->get( $key, $flags ); } diff --git a/includes/libs/objectcache/RESTBagOStuff.php b/includes/libs/objectcache/RESTBagOStuff.php index c127ec6910..7e578f96d9 100644 --- a/includes/libs/objectcache/RESTBagOStuff.php +++ b/includes/libs/objectcache/RESTBagOStuff.php @@ -84,12 +84,15 @@ class RESTBagOStuff extends BagOStuff { $this->client->setLogger( $logger ); } - /** - * @param string $key - * @param int $flags Bitfield of BagOStuff::READ_* constants [optional] - * @return mixed Returns false on failure and if the item does not exist - */ protected function doGet( $key, $flags = 0 ) { + $casToken = null; + + return $this->getWithToken( $key, $casToken, $flags ); + } + + protected function getWithToken( $key, &$casToken, $flags = 0 ) { + $casToken = null; + $req = [ 'method' => 'GET', 'url' => $this->url . rawurlencode( $key ), @@ -98,7 +101,11 @@ class RESTBagOStuff extends BagOStuff { list( $rcode, $rdesc, $rhdrs, $rbody, $rerr ) = $this->client->run( $req ); if ( $rcode === 200 ) { if ( is_string( $rbody ) ) { - return unserialize( $rbody ); + $value = unserialize( $rbody ); + /// @FIXME: use some kind of hash or UUID header as CAS token + $casToken = ( $value !== false ) ? $rbody : null; + + return $value; } return false; } @@ -108,22 +115,6 @@ class RESTBagOStuff extends BagOStuff { return false; } - /** - * Handle storage error - * @param string $msg Error message - * @param int $rcode Error code from client - * @param string $rerr Error message from client - * @return false - */ - protected function handleError( $msg, $rcode, $rerr ) { - $this->logger->error( "$msg : ({code}) {error}", [ - 'code' => $rcode, - 'error' => $rerr - ] ); - $this->setLastError( $rcode === 0 ? self::ERR_UNREACHABLE : self::ERR_UNEXPECTED ); - return false; - } - public function set( $key, $value, $exptime = 0, $flags = 0 ) { // @TODO: respect WRITE_SYNC (e.g. EACH_QUORUM) // @TODO: respect $exptime @@ -172,4 +163,24 @@ class RESTBagOStuff extends BagOStuff { return false; } + + public function merge( $key, callable $callback, $exptime = 0, $attempts = 10, $flags = 0 ) { + return $this->mergeViaCas( $key, $callback, $exptime, $attempts, $flags ); + } + + /** + * Handle storage error + * @param string $msg Error message + * @param int $rcode Error code from client + * @param string $rerr Error message from client + * @return false + */ + protected function handleError( $msg, $rcode, $rerr ) { + $this->logger->error( "$msg : ({code}) {error}", [ + 'code' => $rcode, + 'error' => $rerr + ] ); + $this->setLastError( $rcode === 0 ? self::ERR_UNREACHABLE : self::ERR_UNEXPECTED ); + return false; + } } diff --git a/includes/libs/rdbms/database/DatabaseMysqli.php b/includes/libs/rdbms/database/DatabaseMysqli.php index 1c4ed56d40..15eeccf02c 100644 --- a/includes/libs/rdbms/database/DatabaseMysqli.php +++ b/includes/libs/rdbms/database/DatabaseMysqli.php @@ -78,9 +78,7 @@ class DatabaseMysqli extends DatabaseMysqlBase { } elseif ( substr_count( $realServer, ':' ) == 1 ) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location - $hostAndSocket = explode( ':', $realServer, 2 ); - $realServer = $hostAndSocket[0]; - $socket = $hostAndSocket[1]; + list( $realServer, $socket ) = explode( ':', $realServer, 2 ); } $mysqli = mysqli_init(); diff --git a/includes/media/MediaHandlerFactory.php b/includes/media/MediaHandlerFactory.php index 543dc80dfd..82e8d1ffc2 100644 --- a/includes/media/MediaHandlerFactory.php +++ b/includes/media/MediaHandlerFactory.php @@ -66,11 +66,7 @@ class MediaHandlerFactory { } protected function getHandlerClass( $type ) { - if ( isset( $this->registry[$type] ) ) { - return $this->registry[$type]; - } else { - return false; - } + return $this->registry[$type] ?? false; } /** diff --git a/includes/page/ImagePage.php b/includes/page/ImagePage.php index 66804bc152..60237ff1ab 100644 --- a/includes/page/ImagePage.php +++ b/includes/page/ImagePage.php @@ -321,9 +321,7 @@ class ImagePage extends Article { $dirmark = $lang->getDirMarkEntity(); $request = $this->getContext()->getRequest(); - $max = $this->getImageLimitsFromOption( $user, 'imagesize' ); - $maxWidth = $max[0]; - $maxHeight = $max[1]; + list( $maxWidth, $maxHeight ) = $this->getImageLimitsFromOption( $user, 'imagesize' ); if ( $this->displayImg->exists() ) { # image @@ -1029,7 +1027,7 @@ EOT * * @param User $user * @param string $optionName Name of a option to check, typically imagesize or thumbsize - * @return array + * @return int[] * @since 1.21 */ public function getImageLimitsFromOption( $user, $optionName ) { diff --git a/includes/parser/LinkHolderArray.php b/includes/parser/LinkHolderArray.php index 078c819d6f..64164490c2 100644 --- a/includes/parser/LinkHolderArray.php +++ b/includes/parser/LinkHolderArray.php @@ -632,8 +632,7 @@ class LinkHolderArray { * @private */ public function replaceTextCallback( $matches ) { - $type = $matches[1]; - $key = $matches[2]; + list( , $type, $key ) = $matches; if ( $type == 'LINK' ) { list( $ns, $index ) = explode( ':', $key, 2 ); if ( isset( $this->internals[$ns][$index]['text'] ) ) { diff --git a/includes/parser/Parser.php b/includes/parser/Parser.php index 546152f6c9..0440e89408 100644 --- a/includes/parser/Parser.php +++ b/includes/parser/Parser.php @@ -1045,10 +1045,7 @@ class Parser { $inside = $p[5]; } else { # tag - $element = $p[1]; - $attributes = $p[2]; - $close = $p[3]; - $inside = $p[4]; + list( , $element, $attributes, $close, $inside ) = $p; } $marker = self::MARKER_PREFIX . "-$element-" . sprintf( '%08X', $n++ ) . self::MARKER_SUFFIX; @@ -1072,8 +1069,7 @@ class Parser { $tail = ''; $text = ''; } else { - $tail = $q[1]; - $text = $q[2]; + list( , $tail, $text ) = $q; } } @@ -2240,8 +2236,7 @@ class Parser { if ( $useLinkPrefixExtension ) { if ( preg_match( $e2, $s, $m ) ) { - $prefix = $m[2]; - $s = $m[1]; + list( , $s, $prefix ) = $m; } else { $prefix = ''; } diff --git a/includes/rcfeed/RedisPubSubFeedEngine.php b/includes/rcfeed/RedisPubSubFeedEngine.php index 8a3aa0c104..c954df1a5b 100644 --- a/includes/rcfeed/RedisPubSubFeedEngine.php +++ b/includes/rcfeed/RedisPubSubFeedEngine.php @@ -37,7 +37,7 @@ * * @since 1.22 */ -class RedisPubSubFeedEngine extends RCFeedEngine { +class RedisPubSubFeedEngine extends FormattedRCFeed { /** * @see FormattedRCFeed::send @@ -68,8 +68,8 @@ class RedisPubSubFeedEngine extends RCFeedEngine { if ( $conn !== false ) { $conn->publish( $channel, $line ); return true; - } else { - return false; } + + return false; } } diff --git a/includes/registration/ExtensionProcessor.php b/includes/registration/ExtensionProcessor.php index 1d3fd86d8b..b474ddc766 100644 --- a/includes/registration/ExtensionProcessor.php +++ b/includes/registration/ExtensionProcessor.php @@ -189,7 +189,6 @@ class ExtensionProcessor implements Processor { * @param string $path * @param array $info * @param int $version manifest_version for info - * @return array */ public function extractInfo( $path, array $info, $version ) { $dir = dirname( $path ); diff --git a/includes/revisionlist/RevisionItem.php b/includes/revisionlist/RevisionItem.php new file mode 100644 index 0000000000..faf8d82e79 --- /dev/null +++ b/includes/revisionlist/RevisionItem.php @@ -0,0 +1,135 @@ +revision = new Revision( $row ); + $this->context = $list->getContext(); + } + + public function getIdField() { + return 'rev_id'; + } + + public function getTimestampField() { + return 'rev_timestamp'; + } + + public function getAuthorIdField() { + return 'rev_user'; + } + + public function getAuthorNameField() { + return 'rev_user_text'; + } + + public function canView() { + return $this->revision->userCan( Revision::DELETED_RESTRICTED, $this->context->getUser() ); + } + + public function canViewContent() { + return $this->revision->userCan( Revision::DELETED_TEXT, $this->context->getUser() ); + } + + public function isDeleted() { + return $this->revision->isDeleted( Revision::DELETED_TEXT ); + } + + /** + * Get the HTML link to the revision text. + * @todo Essentially a copy of RevDelRevisionItem::getRevisionLink. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + protected function getRevisionLink() { + $date = $this->list->getLanguage()->userTimeAndDate( + $this->revision->getTimestamp(), $this->list->getUser() ); + + if ( $this->isDeleted() && !$this->canViewContent() ) { + return htmlspecialchars( $date ); + } + $linkRenderer = $this->getLinkRenderer(); + return $linkRenderer->makeKnownLink( + $this->list->title, + $date, + [], + [ + 'oldid' => $this->revision->getId(), + 'unhide' => 1 + ] + ); + } + + /** + * Get the HTML link to the diff. + * @todo Essentially a copy of RevDelRevisionItem::getDiffLink. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + protected function getDiffLink() { + if ( $this->isDeleted() && !$this->canViewContent() ) { + return $this->context->msg( 'diff' )->escaped(); + } else { + $linkRenderer = $this->getLinkRenderer(); + return $linkRenderer->makeKnownLink( + $this->list->title, + $this->list->msg( 'diff' )->text(), + [], + [ + 'diff' => $this->revision->getId(), + 'oldid' => 'prev', + 'unhide' => 1 + ] + ); + } + } + + /** + * @todo Essentially a copy of RevDelRevisionItem::getHTML. That class + * should inherit from this one, and implement an appropriate interface instead + * of extending RevDelItem + * @return string + */ + public function getHTML() { + $difflink = $this->context->msg( 'parentheses' ) + ->rawParams( $this->getDiffLink() )->escaped(); + $revlink = $this->getRevisionLink(); + $userlink = Linker::revUserLink( $this->revision ); + $comment = Linker::revComment( $this->revision ); + if ( $this->isDeleted() ) { + $revlink = "$revlink"; + } + return "
  • $difflink $revlink $userlink $comment
  • "; + } +} diff --git a/includes/revisionlist/RevisionItemBase.php b/includes/revisionlist/RevisionItemBase.php new file mode 100644 index 0000000000..dcb2f39932 --- /dev/null +++ b/includes/revisionlist/RevisionItemBase.php @@ -0,0 +1,177 @@ +list = $list; + $this->row = $row; + } + + /** + * Get the DB field name associated with the ID list. + * Override this function. + * @return null + */ + public function getIdField() { + return null; + } + + /** + * Get the DB field name storing timestamps. + * Override this function. + * @return bool + */ + public function getTimestampField() { + return false; + } + + /** + * Get the DB field name storing user ids. + * Override this function. + * @return bool + */ + public function getAuthorIdField() { + return false; + } + + /** + * Get the DB field name storing user names. + * Override this function. + * @return bool + */ + public function getAuthorNameField() { + return false; + } + + /** + * Get the DB field name storing actor ids. + * Override this function. + * @since 1.31 + * @return bool + */ + public function getAuthorActorField() { + return false; + } + + /** + * Get the ID, as it would appear in the ids URL parameter + * @return int + */ + public function getId() { + $field = $this->getIdField(); + return $this->row->$field; + } + + /** + * Get the date, formatted in user's language + * @return string + */ + public function formatDate() { + return $this->list->getLanguage()->userDate( $this->getTimestamp(), + $this->list->getUser() ); + } + + /** + * Get the time, formatted in user's language + * @return string + */ + public function formatTime() { + return $this->list->getLanguage()->userTime( $this->getTimestamp(), + $this->list->getUser() ); + } + + /** + * Get the timestamp in MW 14-char form + * @return mixed + */ + public function getTimestamp() { + $field = $this->getTimestampField(); + return wfTimestamp( TS_MW, $this->row->$field ); + } + + /** + * Get the author user ID + * @return int + */ + public function getAuthorId() { + $field = $this->getAuthorIdField(); + return intval( $this->row->$field ); + } + + /** + * Get the author user name + * @return string + */ + public function getAuthorName() { + $field = $this->getAuthorNameField(); + return strval( $this->row->$field ); + } + + /** + * Get the author actor ID + * @since 1.31 + * @return string + */ + public function getAuthorActor() { + $field = $this->getAuthorActorField(); + return strval( $this->row->$field ); + } + + /** + * Returns true if the current user can view the item + */ + abstract public function canView(); + + /** + * Returns true if the current user can view the item text/file + */ + abstract public function canViewContent(); + + /** + * Get the HTML of the list item. Should be include "
  • " tags. + * This is used to show the list in HTML form, by the special page. + */ + abstract public function getHTML(); + + /** + * Returns an instance of LinkRenderer + * @return \MediaWiki\Linker\LinkRenderer + */ + protected function getLinkRenderer() { + return MediaWikiServices::getInstance()->getLinkRenderer(); + } +} diff --git a/includes/revisionlist/RevisionList.php b/includes/revisionlist/RevisionList.php new file mode 100644 index 0000000000..e7fab9b8c6 --- /dev/null +++ b/includes/revisionlist/RevisionList.php @@ -0,0 +1,53 @@ + $this->title->getArticleID() ]; + if ( $this->ids !== null ) { + $conds['rev_id'] = array_map( 'intval', $this->ids ); + } + $revQuery = Revision::getQueryInfo( [ 'page', 'user' ] ); + return $db->select( + $revQuery['tables'], + $revQuery['fields'], + $conds, + __METHOD__, + [ 'ORDER BY' => 'rev_id DESC' ], + $revQuery['joins'] + ); + } + + public function newItem( $row ) { + return new RevisionItem( $this, $row ); + } +} diff --git a/includes/revisionlist/RevisionListBase.php b/includes/revisionlist/RevisionListBase.php new file mode 100644 index 0000000000..fb379c953a --- /dev/null +++ b/includes/revisionlist/RevisionListBase.php @@ -0,0 +1,148 @@ +setContext( $context ); + $this->title = $title; + } + + /** + * Select items only where the ID is any of the specified values + * @param array $ids + */ + function filterByIds( array $ids ) { + $this->ids = $ids; + } + + /** + * Get the internal type name of this list. Equal to the table name. + * Override this function. + * @return null + */ + public function getType() { + return null; + } + + /** + * Initialise the current iteration pointer + */ + protected function initCurrent() { + $row = $this->res->current(); + if ( $row ) { + $this->current = $this->newItem( $row ); + } else { + $this->current = false; + } + } + + /** + * Start iteration. This must be called before current() or next(). + * @return Revision First list item + */ + public function reset() { + if ( !$this->res ) { + $this->res = $this->doQuery( wfGetDB( DB_REPLICA ) ); + } else { + $this->res->rewind(); + } + $this->initCurrent(); + return $this->current; + } + + public function rewind() { + $this->reset(); + } + + /** + * Get the current list item, or false if we are at the end + * @return Revision + */ + public function current() { + return $this->current; + } + + /** + * Move the iteration pointer to the next list item, and return it. + * @return Revision + */ + public function next() { + $this->res->next(); + $this->initCurrent(); + return $this->current; + } + + public function key() { + return $this->res ? $this->res->key() : 0; + } + + public function valid() { + return $this->res ? $this->res->valid() : false; + } + + /** + * Get the number of items in the list. + * @return int + */ + public function length() { + if ( !$this->res ) { + return 0; + } else { + return $this->res->numRows(); + } + } + + /** + * Do the DB query to iterate through the objects. + * @param IDatabase $db DB object to use for the query + */ + abstract public function doQuery( $db ); + + /** + * Create an item object from a DB result row + * @param object $row + */ + abstract public function newItem( $row ); +} diff --git a/includes/session/SessionBackend.php b/includes/session/SessionBackend.php index 7956e9fecb..0ea13e2a91 100644 --- a/includes/session/SessionBackend.php +++ b/includes/session/SessionBackend.php @@ -270,6 +270,8 @@ final class SessionBackend { // Delete the data for the old session ID now $this->store->delete( $this->store->makeKey( 'MWSession', $oldId ) ); } + + return $this->id; } /** diff --git a/includes/specials/pagers/ProtectedPagesPager.php b/includes/specials/pagers/ProtectedPagesPager.php index bc4202e2af..5583842497 100644 --- a/includes/specials/pagers/ProtectedPagesPager.php +++ b/includes/specials/pagers/ProtectedPagesPager.php @@ -49,7 +49,7 @@ class ProtectedPagesPager extends TablePager { LinkRenderer $linkRenderer ) { $this->mConds = $conds; - $this->type = ( $type ) ? $type : 'edit'; + $this->type = $type ?: 'edit'; $this->level = $level; $this->namespace = $namespace; $this->sizetype = $sizetype; diff --git a/includes/upload/UploadBase.php b/includes/upload/UploadBase.php index c42584cb1e..d00ad978e7 100644 --- a/includes/upload/UploadBase.php +++ b/includes/upload/UploadBase.php @@ -947,8 +947,8 @@ abstract class UploadBase { */ list( $partname, $ext ) = $this->splitExtensions( $this->mFilteredName ); - if ( count( $ext ) ) { - $this->mFinalExtension = trim( $ext[count( $ext ) - 1] ); + if ( $ext !== [] ) { + $this->mFinalExtension = trim( end( $ext ) ); } else { $this->mFinalExtension = ''; diff --git a/includes/utils/UIDGenerator.php b/includes/utils/UIDGenerator.php index 15c0cf9602..65b50e2d21 100644 --- a/includes/utils/UIDGenerator.php +++ b/includes/utils/UIDGenerator.php @@ -137,8 +137,7 @@ class UIDGenerator { $time = $info['time']; $counter = $info['offsetCounter']; } else { - $time = $info[0]; - $counter = $info[1]; + list( $time, $counter ) = $info; } // Take the 46 LSBs of "milliseconds since epoch" $id_bin = $this->millisecondsSinceEpochBinary( $time ); @@ -192,9 +191,7 @@ class UIDGenerator { $counter = $info['offsetCounter']; $clkSeq = $info['clkSeq']; } else { - $time = $info[0]; - $counter = $info[1]; - $clkSeq = $info[2]; + list( $time, $counter, $clkSeq ) = $info; } // Take the 46 LSBs of "milliseconds since epoch" $id_bin = $this->millisecondsSinceEpochBinary( $time ); diff --git a/languages/classes/LanguageKk_cyrl.php b/languages/classes/LanguageKk_cyrl.php index d695be1049..a89dbc2cac 100644 --- a/languages/classes/LanguageKk_cyrl.php +++ b/languages/classes/LanguageKk_cyrl.php @@ -63,9 +63,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "з" ]; // 1st plural, 2nd formal $thirdPerson = [ "ы", "і" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding =& $lastLetter[0]; - $wordLastVowel =& $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -297,9 +295,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "z" ]; // 1st plural, 2nd formal $thirdPerson = [ "ı", "i" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding =& $lastLetter[0]; - $wordLastVowel =& $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -531,9 +527,7 @@ class LanguageKk_cyrl extends Language { $secondPerson = [ "ز" ]; // 1st plural, 2nd formal $thirdPerson = [ "ى", "Ù¸" ]; // 3rd - $lastLetter = $this->lastLetter( $word, $allVowels ); - $wordEnding = $lastLetter[0]; - $wordLastVowel = $lastLetter[1]; + list( $wordEnding, $wordLastVowel ) = $this->lastLetter( $word, $allVowels ); // Now convert the word switch ( $case ) { @@ -737,7 +731,7 @@ class LanguageKk_cyrl extends Language { /** * @param string $word - * @param array $allVowels + * @param string[] $allVowels * @return array */ function lastLetter( $word, $allVowels ) { diff --git a/maintenance/cleanupInvalidDbKeys.php b/maintenance/cleanupInvalidDbKeys.php index abae4f4379..eb45cfcce8 100644 --- a/maintenance/cleanupInvalidDbKeys.php +++ b/maintenance/cleanupInvalidDbKeys.php @@ -121,8 +121,7 @@ TEXT * @param array $tableParams A child array of self::$tables */ protected function cleanupTable( $tableParams ) { - $table = $tableParams[0]; - $prefix = $tableParams[1]; + list( $table, $prefix ) = $tableParams; $idField = $tableParams['idField'] ?? "{$prefix}_id"; $nsField = $tableParams['nsField'] ?? "{$prefix}_namespace"; $titleField = $tableParams['titleField'] ?? "{$prefix}_title"; diff --git a/maintenance/includes/BackupDumper.php b/maintenance/includes/BackupDumper.php index a9e757e7b4..0b450a64b0 100644 --- a/maintenance/includes/BackupDumper.php +++ b/maintenance/includes/BackupDumper.php @@ -26,6 +26,7 @@ */ require_once __DIR__ . '/../Maintenance.php'; +require_once __DIR__ . '/../../includes/export/WikiExporter.php'; use MediaWiki\MediaWikiServices; use Wikimedia\Rdbms\LoadBalancer; @@ -87,6 +88,7 @@ abstract class BackupDumper extends Maintenance { $this->registerOutput( 'gzip', DumpGZipOutput::class ); $this->registerOutput( 'bzip2', DumpBZip2Output::class ); $this->registerOutput( 'dbzip2', DumpDBZip2Output::class ); + $this->registerOutput( 'lbzip2', DumpLBZip2Output::class ); $this->registerOutput( '7zip', Dump7ZipOutput::class ); $this->registerFilter( 'latest', DumpLatestFilter::class ); @@ -97,7 +99,7 @@ abstract class BackupDumper extends Maintenance { $this->addOption( 'plugin', 'Load a dump plugin class. Specify as [:].', false, true, false, true ); $this->addOption( 'output', 'Begin a filtered output stream; Specify as :. ' . - 's: file, gzip, bzip2, 7zip, dbzip2', false, true, false, true ); + 's: file, gzip, bzip2, 7zip, dbzip2, lbzip2', false, true, false, true ); $this->addOption( 'filter', 'Add a filter on an output branch. Specify as ' . '[:]. s: latest, notalk, namespace', false, true, false, true ); $this->addOption( 'report', 'Report position and speed after every n pages processed. ' . @@ -162,8 +164,7 @@ abstract class BackupDumper extends Maintenance { $options = $this->orderedOptions; foreach ( $options as $arg ) { - $opt = $arg[0]; - $param = $arg[1]; + list( $opt, $param ) = $arg; switch ( $opt ) { case 'plugin': diff --git a/maintenance/mysql.php b/maintenance/mysql.php index 34a6cb6951..c1e403cd3a 100644 --- a/maintenance/mysql.php +++ b/maintenance/mysql.php @@ -137,9 +137,7 @@ class MysqlMaintenance extends Maintenance { } elseif ( substr_count( $realServer, ':' ) == 1 ) { // If we have a colon and something that's not a port number // inside the hostname, assume it's the socket location - $hostAndSocket = explode( ':', $realServer, 2 ); - $realServer = $hostAndSocket[0]; - $socket = $hostAndSocket[1]; + list( $realServer, $socket ) = explode( ':', $realServer, 2 ); } if ( $dbName === false ) { diff --git a/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js b/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js index 63da95a51d..453bf03dbe 100644 --- a/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js +++ b/resources/src/mediawiki.widgets.visibleLengthLimit/mediawiki.widgets.visibleLengthLimit.js @@ -14,12 +14,21 @@ * * @param {OO.ui.TextInputWidget} textInputWidget Text input widget * @param {number} [limit] Byte limit, defaults to $input's maxlength + * @param {Function} [filterFunction] Function to call on the string before assessing the length. */ - mw.widgets.visibleByteLimit = function ( textInputWidget, limit ) { + mw.widgets.visibleByteLimit = function ( textInputWidget, limit, filterFunction ) { limit = limit || +textInputWidget.$input.attr( 'maxlength' ); + if ( !filterFunction || typeof filterFunction !== 'function' ) { + filterFunction = undefined; + } function updateCount() { - var remaining = limit - byteLength( textInputWidget.getValue() ); + var value = textInputWidget.getValue(), + remaining; + if ( filterFunction ) { + value = filterFunction( value ); + } + remaining = limit - byteLength( value ); if ( remaining > 99 ) { remaining = ''; } else { @@ -32,7 +41,7 @@ updateCount(); // Actually enforce limit - textInputWidget.$input.byteLimit( limit ); + textInputWidget.$input.byteLimit( limit, filterFunction ); }; /** @@ -41,13 +50,22 @@ * Uses jQuery#codePointLimit to enforce the limit. * * @param {OO.ui.TextInputWidget} textInputWidget Text input widget - * @param {number} [limit] Byte limit, defaults to $input's maxlength + * @param {number} [limit] Code point limit, defaults to $input's maxlength + * @param {Function} [filterFunction] Function to call on the string before assessing the length. */ - mw.widgets.visibleCodePointLimit = function ( textInputWidget, limit ) { + mw.widgets.visibleCodePointLimit = function ( textInputWidget, limit, filterFunction ) { limit = limit || +textInputWidget.$input.attr( 'maxlength' ); + if ( !filterFunction || typeof filterFunction !== 'function' ) { + filterFunction = undefined; + } function updateCount() { - var remaining = limit - codePointLength( textInputWidget.getValue() ); + var value = textInputWidget.getValue(), + remaining; + if ( filterFunction ) { + value = filterFunction( value ); + } + remaining = limit - codePointLength( value ); if ( remaining > 99 ) { remaining = ''; } else { @@ -60,7 +78,7 @@ updateCount(); // Actually enforce limit - textInputWidget.$input.codePointLimit( limit ); + textInputWidget.$input.codePointLimit( limit, filterFunction ); }; }() ); diff --git a/tests/parser/ParserTestRunner.php b/tests/parser/ParserTestRunner.php index 699de951b6..1c93261a86 100644 --- a/tests/parser/ParserTestRunner.php +++ b/tests/parser/ParserTestRunner.php @@ -938,12 +938,7 @@ class ParserTestRunner { */ private static function getOptionValue( $key, $opts, $default ) { $key = strtolower( $key ); - - if ( isset( $opts[$key] ) ) { - return $opts[$key]; - } else { - return $default; - } + return $opts[$key] ?? $default; } /** diff --git a/tests/phan/config.php b/tests/phan/config.php deleted file mode 100644 index a4654c3227..0000000000 --- a/tests/phan/config.php +++ /dev/null @@ -1,122 +0,0 @@ - $line ) { + $num = $i + 1; $this->assertLessThanOrEqual( // FILE_IGNORE_NEW_LINES drops the \n at the EOL, so max length is 80 not 81. 80, mb_strlen( $line ), - "$type file '$fileName' line $i is longer than 80 chars:\n\t'$line'" + "$type file '$fileName' line $num, is longer than 80 chars:\n\t'$line'" ); } } diff --git a/tests/phpunit/includes/cache/MessageCacheTest.php b/tests/phpunit/includes/cache/MessageCacheTest.php index 8fdc1f1d8f..d8330ef57b 100644 --- a/tests/phpunit/includes/cache/MessageCacheTest.php +++ b/tests/phpunit/includes/cache/MessageCacheTest.php @@ -223,6 +223,8 @@ class MessageCacheTest extends MediaWikiLangTestCase { /** * Regression test for T218918 + * @group Broken + * @fixme Disabled per https://phabricator.wikimedia.org/T219042 */ public function testLoadFromDB_fetchLatestRevision() { // Create three revisions of the same message page. diff --git a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php index b68ffaf8d6..3d8c9cbb0b 100644 --- a/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php +++ b/tests/phpunit/includes/libs/objectcache/BagOStuffTest.php @@ -65,20 +65,10 @@ class BagOStuffTest extends MediaWikiTestCase { /** * @covers BagOStuff::merge - * @covers BagOStuff::mergeViaLock * @covers BagOStuff::mergeViaCas */ public function testMerge() { $key = $this->cache->makeKey( self::TEST_KEY ); - $locks = false; - $checkLockingCallback = function ( BagOStuff $cache, $key, $oldVal ) use ( &$locks ) { - $locks = $cache->get( "$key:lock" ); - - return false; - }; - - $this->cache->merge( $key, $checkLockingCallback, 5 ); - $this->assertFalse( $this->cache->get( $key ) ); $calls = 0; $casRace = false; // emulate a race @@ -103,31 +93,19 @@ class BagOStuffTest extends MediaWikiTestCase { $this->assertEquals( 'mergedmerged', $this->cache->get( $key ) ); $calls = 0; - if ( $locks ) { - // merge were something else already was merging (e.g. had the lock) - $this->cache->lock( $key ); - $this->assertFalse( - $this->cache->merge( $key, $callback, 5, 1 ), - 'Non-blocking merge (locking)' - ); - $this->cache->unlock( $key ); - $this->assertEquals( 0, $calls ); - } else { - $casRace = true; - $this->assertFalse( - $this->cache->merge( $key, $callback, 5, 1 ), - 'Non-blocking merge (CAS)' - ); - $this->assertEquals( 1, $calls ); - } + $casRace = true; + $this->assertFalse( + $this->cache->merge( $key, $callback, 5, 1 ), + 'Non-blocking merge (CAS)' + ); + $this->assertEquals( 1, $calls ); } /** * @covers BagOStuff::merge - * @covers BagOStuff::mergeViaLock * @dataProvider provideTestMerge_fork */ - public function testMerge_fork( $exists, $winsLocking, $resLocking, $resCAS ) { + public function testMerge_fork( $exists, $childWins, $resCAS ) { $key = $this->cache->makeKey( self::TEST_KEY ); $pCallback = function ( BagOStuff $cache, $key, $oldVal ) { return ( $oldVal === false ) ? 'init-parent' : $oldVal . '-merged-parent'; @@ -153,16 +131,12 @@ class BagOStuffTest extends MediaWikiTestCase { $fork &= !$this->cache instanceof MultiWriteBagOStuff; if ( $fork ) { $pid = null; - $locked = false; // Function to start merge(), run another merge() midway through, then finish - $func = function ( BagOStuff $cache, $key, $cur ) - use ( $pCallback, $cCallback, &$pid, &$locked ) - { + $func = function ( $cache, $key, $cur ) use ( $pCallback, $cCallback, &$pid ) { $pid = pcntl_fork(); if ( $pid == -1 ) { return false; } elseif ( $pid ) { - $locked = $cache->get( "$key:lock" ); // parent has lock? pcntl_wait( $status ); return $pCallback( $cache, $key, $cur ); @@ -182,15 +156,9 @@ class BagOStuffTest extends MediaWikiTestCase { return; // can't fork, ignore this test... } - if ( $locked ) { - // merge succeed since child was locked out - $this->assertEquals( $winsLocking, $merged ); - $this->assertEquals( $this->cache->get( $key ), $resLocking ); - } else { - // merge has failed because child process was merging (and we only attempted once) - $this->assertEquals( !$winsLocking, $merged ); - $this->assertEquals( $this->cache->get( $key ), $resCAS ); - } + // merge has failed because child process was merging (and we only attempted once) + $this->assertEquals( !$childWins, $merged ); + $this->assertEquals( $this->cache->get( $key ), $resCAS ); } else { $this->markTestSkipped( 'No pcntl methods available' ); } @@ -198,9 +166,9 @@ class BagOStuffTest extends MediaWikiTestCase { function provideTestMerge_fork() { return [ - // (already exists, parent wins if locking, result if locking, result if CAS) - [ false, true, 'init-parent', 'init-child' ], - [ true, true, 'x-merged-parent', 'x-merged-child' ] + // (already exists, child wins CAS, result of CAS) + [ false, true, 'init-child' ], + [ true, true, 'x-merged-child' ] ]; } diff --git a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php index 4a171dfac0..58f83decba 100644 --- a/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php +++ b/tests/phpunit/includes/specials/QueryAllSpecialPagesTest.php @@ -42,8 +42,7 @@ class QueryAllSpecialPagesTest extends MediaWikiTestCase { parent::__construct(); foreach ( QueryPage::getPages() as $page ) { - $class = $page[0]; - $name = $page[1]; + list( $class, $name ) = $page; if ( !in_array( $class, $this->manualTest ) ) { $this->queryPages[$class] = MediaWikiServices::getInstance()->getSpecialPageFactory()->getPage( $name ); diff --git a/tests/phpunit/structure/SpecialPageFatalTest.php b/tests/phpunit/structure/SpecialPageFatalTest.php index c08fe2fc7f..97797cacf6 100644 --- a/tests/phpunit/structure/SpecialPageFatalTest.php +++ b/tests/phpunit/structure/SpecialPageFatalTest.php @@ -32,8 +32,14 @@ class SpecialPageFatalTest extends MediaWikiTestCase { try { $executor->executeSpecialPage( $page, '', null, null, $user ); + } catch ( \PHPUnit\Framework\Error\Error $error ) { + // Let phpunit settings working: + // - convertErrorsToExceptions="true" + // - convertNoticesToExceptions="true" + // - convertWarningsToExceptions="true" + throw $error; } catch ( Exception $e ) { - // Exceptions are allowed + // Other exceptions are allowed } // If the page fataled phpunit will have already died