diff options
Diffstat (limited to 'includes')
18 files changed, 189 insertions, 315 deletions
diff --git a/includes/Output/OutputPage.php b/includes/Output/OutputPage.php index 0a33bb608ccd..80fd513cc61b 100644 --- a/includes/Output/OutputPage.php +++ b/includes/Output/OutputPage.php @@ -2102,6 +2102,7 @@ class OutputPage extends ContextSource { * @deprecated since 1.44, use ::getMetadata()->setRevisionTimestamp(...) */ public function setRevisionTimestamp( $timestamp ) { + wfDeprecated( __METHOD__, '1.44' ); $previousValue = $this->metadata->getRevisionTimestamp(); $this->metadata->setRevisionTimestamp( $timestamp ); return $previousValue; diff --git a/includes/Rest/i18n/gl.json b/includes/Rest/i18n/gl.json index 54c07b691cca..ee2bdc9ab2eb 100644 --- a/includes/Rest/i18n/gl.json +++ b/includes/Rest/i18n/gl.json @@ -87,6 +87,14 @@ "rest-unsupported-language-conversion": "Conversión de lingua non compatible: $1 a $2", "rest-unknown-content-model": "Modelo de contido descoñecido: $1", "rest-page-bundle-validation-error": "PageBundle non coincide con contentVersion: $1", + "rest-module": "Módulo", + "rest-module-default": "Módulo predeterminado", + "rest-module-extra-routes-title": "API REST de MediaWiki", + "rest-module-extra-routes-desc": "Extremos REST non asociados a ningún módulo", + "rest-module-specs.v0-title": "Especificacións", + "rest-module-specs.v0-desc": "Módulo de autodocumentación que proporciona descubrimento, especificacións e esquemas para todos os módulos dispoñibles.", + "rest-module-content.v1-title": "Contido da páxina", + "rest-module-content.v1-desc": "Ofrece acceso ao contido e metadatos das páxinas e revisións", "rest-param-desc-revision-id": "Identificador da revisión", "rest-param-desc-source": "Contido da páxina no formato especificado pola propiedade content_model" } diff --git a/includes/ServiceWiring.php b/includes/ServiceWiring.php index f82bb3598935..68ec604820dd 100644 --- a/includes/ServiceWiring.php +++ b/includes/ServiceWiring.php @@ -2793,9 +2793,6 @@ return [ ), LoggerFactory::getProvider(), - // UserBlockConstraint - $services->getPermissionManager(), - // EditFilterMergedContentHookConstraint $services->getHookContainer(), @@ -2805,7 +2802,7 @@ return [ // SpamRegexConstraint $services->getSpamChecker(), - // UserRateLimitConstraint + // LinkPurgeRateLimitConstraint $services->getRateLimiter() ); }, diff --git a/includes/api/ApiMessageTrait.php b/includes/api/ApiMessageTrait.php index 8f796101ce2b..9c2bd7575117 100644 --- a/includes/api/ApiMessageTrait.php +++ b/includes/api/ApiMessageTrait.php @@ -67,6 +67,7 @@ trait ApiMessageTrait { 'importuploaderrorpartial' => 'partialupload', 'importuploaderrorsize' => 'filetoobig', 'importuploaderrortemp' => 'notempdir', + 'ipb-block-not-found' => 'alreadyblocked', 'ipb_already_blocked' => 'alreadyblocked', 'ipb_blocked_as_range' => 'blockedasrange', 'ipb_cant_unblock' => 'cantunblock', diff --git a/includes/api/i18n/ja.json b/includes/api/i18n/ja.json index 7a7b7cca92f3..35e9fadef31c 100644 --- a/includes/api/i18n/ja.json +++ b/includes/api/i18n/ja.json @@ -27,12 +27,14 @@ "Yamagata Yusuke", "Yusuke1109", "Yuukin0248", + "もなー(偽物)", "ネイ" ] }, "apihelp-main-extended-description": "<div class=\"hlist plainlinks api-main-links\">\n* [[mw:Special:MyLanguage/API:Main_page|ドキュメンテーション]]\n* [[mw:Special:MyLanguage/API:Etiquette|エチケットと使用ガイドライン]]\n* [[mw:Special:MyLanguage/API:FAQ|よくある質問]]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-api.lists.wikimedia.org/ メーリングリスト]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-api-announce.lists.wikimedia.org/ API 告知]\n* [https://phabricator.wikimedia.org/maniphest/query/GebfyV4uCaLd/#R バグと要望]\n</div>\n<strong>状態:</strong> MediaWiki APIは、サポートおよび更新が恒常的に続けられている、安定した多機能インターフェースです。時折大きな仕様変更が適用されることもありますが、[https://lists.wikimedia.org/hyperkitty/list/mediawiki-api-announce@lists.wikimedia.org/ the mediawiki-api-announce メーリングリスト]を購読すると適時にアップデート通知を受け取ることができます。\n\n<strong>リクエストエラー:</strong> 非適合形式でAPIにリクエストに送られた場合、\"MediaWiki-API-Error\"のキーを含むHTTPヘッダーが返され、レスポンスのヘッダー値とエラーコード値が同値になります。詳細は、[[mw:Special:MyLanguage/API:Errors_and_warnings|API:エラーと警告]]をご覧ください。\n\n<p class=\"mw-apisandbox-link\"><strong>テスト:</strong> [[Special:ApiSandbox|APIサンドボックス]]を使用すると、容易にAPIリクエストのテストが可能です。</p>", "apihelp-main-param-action": "実行する操作。", "apihelp-main-param-format": "出力フォーマット。", + "apihelp-main-param-maxlag": "最大ラグは、MediaWiki がデータベース複製クラスターにインストールされている場合に使用できます。サイトの複製ラグをさらに引き起こすアクションを防ぐために、このパラメータを使用すると、複製ラグが指定値より小さくなるまでクライアントを待機させることができます。ラグが大きすぎる場合は、エラー コード<samp>maxlag</samp>が返され、 <samp>Waiting for $host: $lag seconds lagged</samp>のようなメッセージが表示されます。<br />詳細については、[[mw:Special:MyLanguage/Manual:Maxlag_parameter|マニュアル: Maxlag パラメータ]] を参照してください。", "apihelp-main-param-smaxage": "<code>s-maxage</code>のHTTPキャッシュコントロールヘッダーをこの秒数に指定します。エラーはキャッシュされません。", "apihelp-main-param-maxage": "<code>max-age</code>のHTTPキャッシュコントロールヘッダーをこの秒数に指定します。エラーはキャッシュされません。", "apihelp-main-param-assert": "<kbd>user</kbd> に設定した場合は利用者がログインしていること(臨時利用者としてログインしている場合も含む)を、 <kbd>anon</kbd> に設定した場合はログインして<em>いない</em>ことを、<kbd>bot</kbd> の場合はボット利用者権限があることを検証します。", @@ -52,6 +54,7 @@ "apihelp-main-param-errorlang": "警告およびエラーで使用する言語。 <kbd>[[Special:ApiHelp/query+siteinfo|action=query&meta=siteinfo&siprop=languages]]</kbd> は言語コードの一覧を返します。 <kbd>content</kbd> を指定するとこのウィキのコンテンツ言語、<kbd>uselang</kbd> を指定すると <var>uselang</var> パラメーターと同じ値を使用します。", "apihelp-main-param-errorsuselocal": "指定された場合、エラーテキストは{{ns:MediaWiki}}名前空間からローカルにカスタマイズされたメッセージを使用します。", "apihelp-block-summary": "利用者をブロックします。", + "apihelp-block-param-id": "変更するブロックID。", "apihelp-block-param-user": "ブロックする利用者。", "apihelp-block-param-userid": "代わりに <kbd>$1user=#<var>ID</var></kbd> を指定してください。", "apihelp-block-param-expiry": "有効期限。相対的 (例: <kbd>5 months</kbd> または <kbd>2 weeks</kbd>) または絶対的 (e.g. <kbd>2014-09-18T12:34:56Z</kbd>) どちらでも構いません。<kbd>infinite</kbd>, <kbd>indefinite</kbd>, もしくは <kbd>never</kbd> と設定した場合, 無期限ブロックとなります。", @@ -63,18 +66,26 @@ "apihelp-block-param-hidename": "ブロック記録から利用者名を秘匿します。(<code>hideuser</code> 権限が必要です)", "apihelp-block-param-allowusertalk": "自身のトークページの編集を許可する (<var>[[mw:Special:MyLanguage/Manual:$wgBlockAllowsUTEdit|$wgBlockAllowsUTEdit]]</var> に依存)。", "apihelp-block-param-reblock": "その利用者がすでにブロックされている場合、ブロックを上書きします。", + "apihelp-block-param-newblock": "利用者がすでにブロックされている場合でも、別のブロックを追加します。", "apihelp-block-param-watchuser": "その利用者またはIPアドレスの利用者ページとトークページをウォッチします。", "apihelp-block-param-watchlistexpiry": "ウォッチリストの有効期限のタイムスタンプ。現在の有効期限を変更せずそのままにするには、このパラメーターを完全に省略します。", "apihelp-block-param-tags": "ブロック記録の項目に適用する変更タグ。", "apihelp-block-param-partial": "サイト全体ではなく特定のページまたは名前空間での編集をブロックします。", "apihelp-block-param-pagerestrictions": "利用者が編集できないようにするページのタイトルのリスト。<var>partial</var> に true が設定されている場合のみ適用します。", "apihelp-block-param-namespacerestrictions": "利用者が編集できないようにする名前空間のID。<var>partial</var> に true が設定されている場合のみ適用します。", + "apihelp-block-param-actionrestrictions": "利用者に実行させない操作のリスト。<var>partial</var> が true に設定されている場合のみ適用されます。", "apihelp-block-example-ip-simple": "IPアドレス <kbd>192.0.2.5</kbd> を何らかの理由で3日ブロックする。", "apihelp-block-example-user-complex": "利用者 <kbd>Vandal</kbd> を何らかの理由で無期限ブロックし、新たなアカウント作成とメールの送信を禁止する。", "apihelp-changeauthenticationdata-summary": "現在の利用者の認証データを変更します。", "apihelp-changeauthenticationdata-example-password": "現在の利用者のパスワードを <kbd>ExamplePassword</kbd> に変更する。", "apihelp-changecontentmodel-summary": "ページのコンテンツモデルを変更します。", + "apihelp-changecontentmodel-param-title": "コンテンツモデルを変更するページ名。<var>$1pageid</var>と併用することはできません。", + "apihelp-changecontentmodel-param-pageid": "コンテンツモデルを変更するページID。<var>$1title</var> とは併用できません。", + "apihelp-changecontentmodel-param-summary": "編集の概要と記録エントリの理由", + "apihelp-changecontentmodel-param-tags": "記録エントリに適用するタグを変更して編集します。", "apihelp-changecontentmodel-param-model": "新しいコンテンツのコンテンツモデル。", + "apihelp-changecontentmodel-param-bot": "コンテンツモデルの変更をボット フラグでマークします。", + "apihelp-changecontentmodel-example": "メインページが<code>text</code>コンテンツモデルを持つよう変更する", "apihelp-checktoken-summary": "<kbd>[[Special:ApiHelp/query+tokens|action=query&meta=tokens]]</kbd> のトークンの妥当性を確認します。", "apihelp-checktoken-param-type": "調べるトークンの種類。", "apihelp-checktoken-param-token": "調べるトークン。", diff --git a/includes/block/BlockUser.php b/includes/block/BlockUser.php index aa9ac9269fd4..3de0388d1611 100644 --- a/includes/block/BlockUser.php +++ b/includes/block/BlockUser.php @@ -587,7 +587,6 @@ class BlockUser { } $expectedTargetCount = 0; - $priorBlock = null; $priorBlocks = $this->getPriorBlocksForTarget(); if ( $this->blockToUpdate !== null ) { @@ -601,7 +600,7 @@ class BlockUser { } elseif ( $conflictMode === self::CONFLICT_NEW && $this->options->get( MainConfigNames::EnableMultiBlocks ) ) { - foreach ( $this->getPriorBlocksForTarget() as $priorBlock ) { + foreach ( $priorBlocks as $priorBlock ) { if ( $block->equals( $priorBlock ) ) { // Block settings are equal => user is already blocked $this->logger->debug( 'placeBlockInternal: ' . @@ -610,8 +609,10 @@ class BlockUser { } } $expectedTargetCount = null; + $priorBlock = null; $update = false; } elseif ( !$priorBlocks ) { + $priorBlock = null; $update = false; } else { // Reblock only if the caller wants so diff --git a/includes/block/DatabaseBlockStore.php b/includes/block/DatabaseBlockStore.php index c33157b58157..20f662e044d5 100644 --- a/includes/block/DatabaseBlockStore.php +++ b/includes/block/DatabaseBlockStore.php @@ -1044,6 +1044,7 @@ class DatabaseBlockStore { $targetUserName = (string)$target; $targetUserId = $target->getUserIdentity()->getId( $this->wikiId ); $targetConds = [ 'bt_user' => $targetUserId ]; + $targetLockKey = $dbw->getDomainID() . ':block:u:' . $targetUserId; } else { $targetAddress = (string)$target; $targetUserName = null; @@ -1052,6 +1053,8 @@ class DatabaseBlockStore { 'bt_address' => $targetAddress, 'bt_auto' => $isAuto, ]; + $targetLockKey = $dbw->getDomainID() . ':block:' . + ( $isAuto ? 'a' : 'i' ) . ':' . $targetAddress; } $condsWithCount = $targetConds; @@ -1059,6 +1062,15 @@ class DatabaseBlockStore { $condsWithCount['bt_count'] = $expectedTargetCount; } + $dbw->lock( $targetLockKey, __METHOD__ ); + $func = __METHOD__; + $dbw->onTransactionCommitOrIdle( + static function () use ( $dbw, $targetLockKey, $func ) { + $dbw->unlock( $targetLockKey, "$func.closure" ); + }, + __METHOD__ + ); + // This query locks the index gap when the target doesn't exist yet, // so there is a risk of throttling adjacent block insertions, // especially on small wikis which have larger gaps. If this proves to @@ -1076,6 +1088,7 @@ class DatabaseBlockStore { ->select( [ 'bt_id', 'bt_count' ] ) ->from( 'block_target' ) ->where( $targetConds ) + ->forUpdate() ->caller( __METHOD__ ) ->fetchResultSet(); if ( $res->numRows() > 1 ) { diff --git a/includes/diff/DifferenceEngine.php b/includes/diff/DifferenceEngine.php index faca5c529263..e5a3b1428525 100644 --- a/includes/diff/DifferenceEngine.php +++ b/includes/diff/DifferenceEngine.php @@ -1238,7 +1238,7 @@ class DifferenceEngine extends ContextSource { $out->setRevisionId( $this->mNewid ); $out->setRevisionIsCurrent( $this->mNewRevisionRecord->isCurrent() ); - $out->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() ); + $out->getMetadata()->setRevisionTimestamp( $this->mNewRevisionRecord->getTimestamp() ); $out->setArticleFlag( true ); if ( !$this->hookRunner->onArticleRevisionViewCustom( diff --git a/includes/editpage/Constraint/AuthorizationConstraint.php b/includes/editpage/Constraint/AuthorizationConstraint.php new file mode 100644 index 000000000000..73247160fa7b --- /dev/null +++ b/includes/editpage/Constraint/AuthorizationConstraint.php @@ -0,0 +1,88 @@ +<?php +/** + * 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 + */ + +namespace MediaWiki\EditPage\Constraint; + +use MediaWiki\Page\PageIdentity; +use MediaWiki\Permissions\Authority; +use MediaWiki\Permissions\PermissionStatus; +use StatusValue; + +/** + * Verify authorization to edit the page (user rights, rate limits, blocks). + * + * @since 1.44 + * @internal + */ +class AuthorizationConstraint implements IEditConstraint { + + private PermissionStatus $status; + + private Authority $performer; + private PageIdentity $target; + private bool $new; + + public function __construct( + Authority $performer, + PageIdentity $target, + bool $new + ) { + $this->performer = $performer; + $this->target = $target; + $this->new = $new; + } + + public function checkConstraint(): string { + $this->status = PermissionStatus::newEmpty(); + + if ( $this->new && !$this->performer->authorizeWrite( 'create', $this->target, $this->status ) ) { + return self::CONSTRAINT_FAILED; + } + + if ( !$this->performer->authorizeWrite( 'edit', $this->target, $this->status ) ) { + return self::CONSTRAINT_FAILED; + } + + return self::CONSTRAINT_PASSED; + } + + public function getLegacyStatus(): StatusValue { + $statusValue = StatusValue::newGood(); + + if ( !$this->status->isGood() ) { + // Report the most specific errors first + if ( $this->status->isBlocked() ) { + $statusValue->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER ); + } elseif ( $this->status->isRateLimitExceeded() ) { + $statusValue->setResult( false, self::AS_RATE_LIMITED ); + } elseif ( $this->status->getPermission() === 'create' ) { + $statusValue->setResult( false, self::AS_NO_CREATE_PERMISSION ); + } elseif ( !$this->performer->isRegistered() ) { + $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_ANON ); + } else { + $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_LOGGED ); + } + } + + // TODO: Use error messages from the PermissionStatus ($this->status) here - T384399 + return $statusValue; + } + +} diff --git a/includes/editpage/Constraint/ContentModelChangeConstraint.php b/includes/editpage/Constraint/ContentModelChangeConstraint.php index 8ed2e56debde..2581ebab3c14 100644 --- a/includes/editpage/Constraint/ContentModelChangeConstraint.php +++ b/includes/editpage/Constraint/ContentModelChangeConstraint.php @@ -21,6 +21,7 @@ namespace MediaWiki\EditPage\Constraint; use MediaWiki\Permissions\Authority; +use MediaWiki\Permissions\PermissionStatus; use MediaWiki\Title\Title; use StatusValue; @@ -28,6 +29,7 @@ use StatusValue; * Verify user permissions if changing content model: * Must have editcontentmodel rights * Must be able to edit under the new content model + * Must not have exceeded the rate limit * * @since 1.36 * @internal @@ -35,10 +37,11 @@ use StatusValue; */ class ContentModelChangeConstraint implements IEditConstraint { + private PermissionStatus $status; + private Authority $performer; private Title $title; private string $newContentModel; - private string $result; /** * @param Authority $performer @@ -56,45 +59,43 @@ class ContentModelChangeConstraint implements IEditConstraint { } public function checkConstraint(): string { + $this->status = PermissionStatus::newEmpty(); + if ( $this->newContentModel === $this->title->getContentModel() ) { // No change - $this->result = self::CONSTRAINT_PASSED; return self::CONSTRAINT_PASSED; } - if ( !$this->performer->isAllowed( 'editcontentmodel' ) ) { - $this->result = self::CONSTRAINT_FAILED; + if ( !$this->performer->authorizeWrite( 'editcontentmodel', $this->title, $this->status ) ) { return self::CONSTRAINT_FAILED; } - // Make sure the user can edit the page under the new content model too + // Make sure the user can edit the page under the new content model too. + // We rely on caching in UserAuthority to avoid bumping the rate limit counter twice. $titleWithNewContentModel = clone $this->title; $titleWithNewContentModel->setContentModel( $this->newContentModel ); - - $canEditModel = $this->performer->authorizeWrite( - 'editcontentmodel', - $titleWithNewContentModel - ); - if ( - !$canEditModel - || !$this->performer->authorizeWrite( 'edit', $titleWithNewContentModel ) + !$this->performer->authorizeWrite( 'editcontentmodel', $titleWithNewContentModel, $this->status ) + || !$this->performer->authorizeWrite( 'edit', $titleWithNewContentModel, $this->status ) ) { - $this->result = self::CONSTRAINT_FAILED; return self::CONSTRAINT_FAILED; } - $this->result = self::CONSTRAINT_PASSED; return self::CONSTRAINT_PASSED; } public function getLegacyStatus(): StatusValue { $statusValue = StatusValue::newGood(); - if ( $this->result === self::CONSTRAINT_FAILED ) { - $statusValue->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL ); + if ( !$this->status->isGood() ) { + if ( $this->status->isRateLimitExceeded() ) { + $statusValue->setResult( false, self::AS_RATE_LIMITED ); + } else { + $statusValue->setResult( false, self::AS_NO_CHANGE_CONTENT_MODEL ); + } } + // TODO: Use error messages from the PermissionStatus ($this->status) here - T384399 return $statusValue; } diff --git a/includes/editpage/Constraint/EditConstraintFactory.php b/includes/editpage/Constraint/EditConstraintFactory.php index 750c977d2312..e9fede9f256c 100644 --- a/includes/editpage/Constraint/EditConstraintFactory.php +++ b/includes/editpage/Constraint/EditConstraintFactory.php @@ -26,10 +26,8 @@ use MediaWiki\Context\IContextSource; use MediaWiki\EditPage\SpamChecker; use MediaWiki\HookContainer\HookContainer; use MediaWiki\Language\Language; -use MediaWiki\Linker\LinkTarget; use MediaWiki\Logger\Spi; use MediaWiki\MainConfigNames; -use MediaWiki\Permissions\PermissionManager; use MediaWiki\Permissions\RateLimiter; use MediaWiki\Permissions\RateLimitSubject; use MediaWiki\Title\Title; @@ -54,7 +52,6 @@ class EditConstraintFactory { private ServiceOptions $options; private Spi $loggerFactory; - private PermissionManager $permissionManager; private HookContainer $hookContainer; private ReadOnlyMode $readOnlyMode; private SpamChecker $spamRegexChecker; @@ -74,7 +71,6 @@ class EditConstraintFactory { * * @param ServiceOptions $options * @param Spi $loggerFactory - * @param PermissionManager $permissionManager * @param HookContainer $hookContainer * @param ReadOnlyMode $readOnlyMode * @param SpamChecker $spamRegexChecker @@ -83,7 +79,6 @@ class EditConstraintFactory { public function __construct( ServiceOptions $options, Spi $loggerFactory, - PermissionManager $permissionManager, HookContainer $hookContainer, ReadOnlyMode $readOnlyMode, SpamChecker $spamRegexChecker, @@ -95,9 +90,6 @@ class EditConstraintFactory { $this->options = $options; $this->loggerFactory = $loggerFactory; - // UserBlockConstraint - $this->permissionManager = $permissionManager; - // EditFilterMergedContentHookConstraint $this->hookContainer = $hookContainer; @@ -107,7 +99,7 @@ class EditConstraintFactory { // SpamRegexConstraint $this->spamRegexChecker = $spamRegexChecker; - // UserRateLimitConstraint + // LinkPurgeRateLimitConstraint $this->rateLimiter = $rateLimiter; } @@ -163,21 +155,15 @@ class EditConstraintFactory { /** * @param RateLimitSubject $subject - * @param string $oldModel - * @param string $newModel * - * @return UserRateLimitConstraint + * @return LinkPurgeRateLimitConstraint */ - public function newUserRateLimitConstraint( - RateLimitSubject $subject, - string $oldModel, - string $newModel - ): UserRateLimitConstraint { - return new UserRateLimitConstraint( + public function newLinkPurgeRateLimitConstraint( + RateLimitSubject $subject + ): LinkPurgeRateLimitConstraint { + return new LinkPurgeRateLimitConstraint( $this->rateLimiter, - $subject, - $oldModel, - $newModel + $subject ); } @@ -226,39 +212,4 @@ class EditConstraintFactory { ); } - /** - * @param LinkTarget $title - * @param User $user - * @return UserBlockConstraint - */ - public function newUserBlockConstraint( - LinkTarget $title, - User $user - ): UserBlockConstraint { - return new UserBlockConstraint( - $this->permissionManager, - $title, - $user - ); - } - - /** - * @param User $performer - * @param Title $title - * @param bool $new - * @return EditRightConstraint - */ - public function newEditRightConstraint( - User $performer, - Title $title, - bool $new - ): EditRightConstraint { - return new EditRightConstraint( - $performer, - $this->permissionManager, - $title, - $new - ); - } - } diff --git a/includes/editpage/Constraint/EditRightConstraint.php b/includes/editpage/Constraint/EditRightConstraint.php deleted file mode 100644 index 47be037e4dad..000000000000 --- a/includes/editpage/Constraint/EditRightConstraint.php +++ /dev/null @@ -1,105 +0,0 @@ -<?php -/** - * 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 - */ - -namespace MediaWiki\EditPage\Constraint; - -use MediaWiki\Permissions\PermissionManager; -use MediaWiki\Title\Title; -use MediaWiki\User\User; -use StatusValue; - -/** - * Verify user permissions: - * Must have edit rights - * - * @since 1.36 - * @internal - * @author DannyS712 - */ -class EditRightConstraint implements IEditConstraint { - - private User $performer; - private PermissionManager $permManager; - private Title $title; - private string $result; - private bool $new; - - /** - * @param User $performer - * @param PermissionManager $permManager - * @param Title $title - * @param bool $new - */ - public function __construct( - User $performer, - PermissionManager $permManager, - Title $title, - bool $new - ) { - $this->performer = $performer; - $this->permManager = $permManager; - $this->title = $title; - $this->new = $new; - } - - public function checkConstraint(): string { - if ( $this->new ) { - // Check isn't simple enough to just repeat when getting the status - if ( !$this->performer->authorizeWrite( 'create', $this->title ) ) { - $this->result = (string)self::AS_NO_CREATE_PERMISSION; - return self::CONSTRAINT_FAILED; - } - } - - // Check isn't simple enough to just repeat when getting the status - // Prior to 1.41 this checked if the user had edit rights in general - // instead of for the specific page in question. - if ( !$this->permManager->userCan( - 'edit', - $this->performer, - $this->title - ) ) { - $this->result = self::CONSTRAINT_FAILED; - return self::CONSTRAINT_FAILED; - } - - $this->result = self::CONSTRAINT_PASSED; - return self::CONSTRAINT_PASSED; - } - - public function getLegacyStatus(): StatusValue { - $statusValue = StatusValue::newGood(); - - if ( $this->result === self::CONSTRAINT_FAILED ) { - if ( !$this->performer->isRegistered() ) { - $statusValue->setResult( false, self::AS_READ_ONLY_PAGE_ANON ); - } else { - $statusValue->fatal( 'readonlytext' ); - $statusValue->value = self::AS_READ_ONLY_PAGE_LOGGED; - } - } elseif ( $this->result === (string)self::AS_NO_CREATE_PERMISSION ) { - $statusValue->fatal( 'nocreatetext' ); - $statusValue->value = self::AS_NO_CREATE_PERMISSION; - } - - return $statusValue; - } - -} diff --git a/includes/editpage/Constraint/UserRateLimitConstraint.php b/includes/editpage/Constraint/LinkPurgeRateLimitConstraint.php index 8f35dd5879d0..d25cf9336d88 100644 --- a/includes/editpage/Constraint/UserRateLimitConstraint.php +++ b/includes/editpage/Constraint/LinkPurgeRateLimitConstraint.php @@ -25,31 +25,26 @@ use MediaWiki\Permissions\RateLimitSubject; use StatusValue; /** - * Verify user doesn't exceed rate limits + * Verify that the user doesn't exceed 'linkpurge' limits, which are weird and special. + * Other rate limits have been integrated into their respective permission checks. * - * @since 1.36 + * @since 1.44 * @internal * @author DannyS712 */ -class UserRateLimitConstraint implements IEditConstraint { +class LinkPurgeRateLimitConstraint implements IEditConstraint { private RateLimitSubject $subject; - private string $oldContentModel; - private string $newContentModel; private RateLimiter $limiter; private string $result; public function __construct( RateLimiter $limiter, - RateLimitSubject $subject, - string $oldContentModel, - string $newContentModel + RateLimitSubject $subject ) { $this->limiter = $limiter; $this->subject = $subject; - $this->oldContentModel = $oldContentModel; - $this->newContentModel = $newContentModel; } private function limit( string $action, int $inc = 1 ): bool { @@ -57,16 +52,10 @@ class UserRateLimitConstraint implements IEditConstraint { } public function checkConstraint(): string { - // Need to check for rate limits on `editcontentmodel` if it is changing - $contentModelChange = ( $this->newContentModel !== $this->oldContentModel ); - // TODO inject and use a ThrottleStore once available, see T261744 // Checking if the user is rate limited increments the counts, so we cannot perform // the check again when getting the status; thus, store the result - if ( $this->limit( 'edit' ) - || $this->limit( 'linkpurge', 0 ) // only counted after the fact - || ( $contentModelChange && $this->limit( 'editcontentmodel' ) ) - ) { + if ( $this->limit( 'linkpurge', /* only counted after the fact */ 0 ) ) { $this->result = self::CONSTRAINT_FAILED; } else { $this->result = self::CONSTRAINT_PASSED; diff --git a/includes/editpage/Constraint/UserBlockConstraint.php b/includes/editpage/Constraint/UserBlockConstraint.php deleted file mode 100644 index bdd33262d434..000000000000 --- a/includes/editpage/Constraint/UserBlockConstraint.php +++ /dev/null @@ -1,79 +0,0 @@ -<?php -/** - * 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 - */ - -namespace MediaWiki\EditPage\Constraint; - -use MediaWiki\Linker\LinkTarget; -use MediaWiki\Permissions\PermissionManager; -use MediaWiki\User\User; -use StatusValue; - -/** - * Verify user permissions: - * Must not be blocked from the page - * - * @since 1.36 - * @internal - * @author DannyS712 - */ -class UserBlockConstraint implements IEditConstraint { - - private PermissionManager $permissionManager; - private LinkTarget $title; - private User $user; - private string $result; - - /** - * @param PermissionManager $permissionManager - * @param LinkTarget $title - * @param User $user - */ - public function __construct( - PermissionManager $permissionManager, - LinkTarget $title, - User $user - ) { - $this->permissionManager = $permissionManager; - $this->title = $title; - $this->user = $user; - } - - public function checkConstraint(): string { - // Check isn't simple enough to just repeat when getting the status - if ( $this->permissionManager->isBlockedFrom( $this->user, $this->title ) ) { - $this->result = self::CONSTRAINT_FAILED; - return self::CONSTRAINT_FAILED; - } - - $this->result = self::CONSTRAINT_PASSED; - return self::CONSTRAINT_PASSED; - } - - public function getLegacyStatus(): StatusValue { - $statusValue = StatusValue::newGood(); - - if ( $this->result === self::CONSTRAINT_FAILED ) { - $statusValue->setResult( false, self::AS_BLOCKED_PAGE_FOR_USER ); - } - - return $statusValue; - } - -} diff --git a/includes/editpage/EditPage.php b/includes/editpage/EditPage.php index 93a3ad5a28e8..f08196894d88 100644 --- a/includes/editpage/EditPage.php +++ b/includes/editpage/EditPage.php @@ -37,6 +37,7 @@ use MediaWiki\Context\IContextSource; use MediaWiki\Debug\DeprecationHelper; use MediaWiki\Deferred\DeferredUpdates; use MediaWiki\EditPage\Constraint\AccidentalRecreationConstraint; +use MediaWiki\EditPage\Constraint\AuthorizationConstraint; use MediaWiki\EditPage\Constraint\BrokenRedirectConstraint; use MediaWiki\EditPage\Constraint\ChangeTagsConstraint; use MediaWiki\EditPage\Constraint\ContentModelChangeConstraint; @@ -54,7 +55,6 @@ use MediaWiki\EditPage\Constraint\PageSizeConstraint; use MediaWiki\EditPage\Constraint\SelfRedirectConstraint; use MediaWiki\EditPage\Constraint\SpamRegexConstraint; use MediaWiki\EditPage\Constraint\UnicodeConstraint; -use MediaWiki\EditPage\Constraint\UserBlockConstraint; use MediaWiki\Exception\ErrorPageError; use MediaWiki\Exception\MWContentSerializationException; use MediaWiki\Exception\MWException; @@ -2189,24 +2189,31 @@ class EditPage implements IEditObject { ) ); $constraintRunner->addConstraint( - $constraintFactory->newUserBlockConstraint( $this->mTitle, $requestUser ) + $constraintFactory->newReadOnlyConstraint() ); + + // Load the page data from the primary DB. If anything changes in the meantime, + // we detect it by using page_latest like a token in a 1 try compare-and-swap. + $this->page->loadPageData( IDBAccessObject::READ_LATEST ); + $new = !$this->page->exists(); + $constraintRunner->addConstraint( - new ContentModelChangeConstraint( + new AuthorizationConstraint( $authority, $this->mTitle, - $this->contentModel + $new ) ); - $constraintRunner->addConstraint( - $constraintFactory->newReadOnlyConstraint() + new ContentModelChangeConstraint( + $authority, + $this->mTitle, + $this->contentModel + ) ); $constraintRunner->addConstraint( - $constraintFactory->newUserRateLimitConstraint( - $requestUser->toRateLimitSubject(), - $this->mTitle->getContentModel(), - $this->contentModel + $constraintFactory->newLinkPurgeRateLimitConstraint( + $requestUser->toRateLimitSubject() ) ); $constraintRunner->addConstraint( @@ -2230,16 +2237,6 @@ class EditPage implements IEditObject { ) ); - // Load the page data from the primary DB. If anything changes in the meantime, - // we detect it by using page_latest like a token in a 1 try compare-and-swap. - $this->page->loadPageData( IDBAccessObject::READ_LATEST ); - $new = !$this->page->exists(); - - // We do this last, as some of the other constraints are more specific - $constraintRunner->addConstraint( - $constraintFactory->newEditRightConstraint( $this->getUserForPermissions(), $this->mTitle, $new ) - ); - // Check the constraints if ( !$constraintRunner->checkConstraints() ) { $failed = $constraintRunner->getFailedConstraint(); @@ -2625,9 +2622,12 @@ class EditPage implements IEditObject { * result from the backend. */ private function handleFailedConstraint( IEditConstraint $failed ): void { - if ( $failed instanceof UserBlockConstraint ) { + if ( $failed instanceof AuthorizationConstraint ) { // Auto-block user's IP if the account was "hard" blocked - if ( !MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() ) { + if ( + !MediaWikiServices::getInstance()->getReadOnlyMode()->isReadOnly() + && $failed->getLegacyStatus()->value === self::AS_BLOCKED_PAGE_FOR_USER + ) { $this->context->getUser()->spreadAnyEditBlock(); } } elseif ( $failed instanceof DefaultTextConstraint ) { diff --git a/includes/installer/i18n/nan-hant.json b/includes/installer/i18n/nan-hant.json index 7ca51f8702fc..59cbdd5709e9 100644 --- a/includes/installer/i18n/nan-hant.json +++ b/includes/installer/i18n/nan-hant.json @@ -15,7 +15,7 @@ "config-no-session": "你連線的資料已經無去矣,看你的php.ini,並且確定<code>session.save_path</code>是正確的目錄。", "config-your-language": "你的語言:", "config-your-language-help": "選一个安裝過程時欲用的話語", - "config-wiki-language": "Wiki話語", + "config-wiki-language": "wiki語言:", "config-wiki-language-help": "選一个Wiki大部份用的話", "config-back": "← 倒退", "config-continue": "繼續 →", @@ -42,5 +42,6 @@ "config-header-postgres": "PostgreSQL設定", "config-header-sqlite": "SQLite設定", "config-project-namespace": "專案名空間:", - "config-ns-generic": "專案" + "config-ns-generic": "專案", + "mainpagedocfooter": "請查看[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 用者說明書]的資料通使用wiki 軟體\n\n== 入門 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置的設定]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki時常問答]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki的公布列單]" } diff --git a/includes/installer/i18n/nan-latn-pehoeji.json b/includes/installer/i18n/nan-latn-pehoeji.json index e3437e4d4805..8430b8f9272a 100644 --- a/includes/installer/i18n/nan-latn-pehoeji.json +++ b/includes/installer/i18n/nan-latn-pehoeji.json @@ -22,13 +22,10 @@ "config-session-error": "連線開始了的錯誤:$1", "config-session-expired": "你連線資料已經過時矣,連線的使用期限是設做$1。你會使改共php.ini的<code>session.gc_maxlifetime</code>改較長,並且重新安裝動作。", "config-no-session": "你連線的資料已經無去矣,看你的php.ini,並且確定<code>session.save_path</code>是正確的目錄。", - "config-your-language": "你的話語:", - "config-your-language-help": "選一个安裝過程時欲用的話語", - "config-wiki-language": "Wiki話語", - "config-wiki-language-help": "選一个Wiki大部份用的話", + "config-wiki-language": "Wiki gí-giân:", "config-back": "← 倒退", "config-continue": "繼續 →", - "config-page-language": "話語", + "config-page-language": "Gí-giân", "config-page-welcome": "歡迎來MediaWiki!", "config-page-dbconnect": "連接去資料庫", "config-page-upgrade": "共這馬的安裝升級", @@ -53,6 +50,5 @@ "config-no-db": "揣無適合的資料庫驅動程式!你需要安裝 PHP 資料庫驅動程式。\n這馬支援下跤類型的資料庫: $1 。\n\n若你是家己編譯 PHP,你需要重新設定並且開資料庫客戶端,譬如:用 <code>./configure --with-mysqli</code> 指令參數。\n如你是用 Debian 或 Ubuntu 的套件安裝,你著需要閣另外安裝,例:<code>php-mysql</code> 套件。", "config-outdated-sqlite": "<strong>Kéng-kò:</strong> Lí í-keng an-chng SQLite $1, m̄-koh i--ê pán-pún pí thang-chng--ê pán-pún $2 khah kū. Só͘-í lí bô-hoat-tō͘ ēng SQLite.", "config-no-fts3": "<strong>Kéng-kò: </strong> SQLite tī pian-e̍k--ê sî-chūn bô pau-koat [//sqlite.org/fts3.html FTS3 module], āu-tâi chhiau-chhoē kong-lêng tiō ē bô-hoat-tō͘ iōng.", - "mainpagetext": "'''MediaWiki已經裝好矣。'''", - "mainpagedocfooter": "請查看[https://www.mediawiki.org/wiki/Special:MyLanguage/Help:Contents 用者說明書]的資料通使用wiki 軟體\n\n== 入門 ==\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:Configuration_settings 配置的設定]\n* [https://www.mediawiki.org/wiki/Special:MyLanguage/Manual:FAQ MediaWiki時常問答]\n* [https://lists.wikimedia.org/postorius/lists/mediawiki-announce.lists.wikimedia.org/ MediaWiki的公布列單]" + "mainpagetext": "'''MediaWiki已經裝好矣。'''" } diff --git a/includes/page/Article.php b/includes/page/Article.php index c6ff94f24503..962990012952 100644 --- a/includes/page/Article.php +++ b/includes/page/Article.php @@ -784,7 +784,7 @@ class Article implements Page { $outputPage->setRevisionId( $this->getRevIdFetched() ); $outputPage->setRevisionIsCurrent( $rev->isCurrent() ); # Preload timestamp to avoid a DB hit - $outputPage->setRevisionTimestamp( $rev->getTimestamp() ); + $outputPage->getMetadata()->setRevisionTimestamp( $rev->getTimestamp() ); # Pages containing custom CSS or JavaScript get special treatment if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) { @@ -929,7 +929,7 @@ class Article implements Page { # Preload timestamp to avoid a DB hit $cachedTimestamp = $pOutput->getRevisionTimestamp(); if ( $cachedTimestamp !== null ) { - $outputPage->setRevisionTimestamp( $cachedTimestamp ); + $outputPage->getMetadata()->setRevisionTimestamp( $cachedTimestamp ); $this->mPage->setTimestamp( $cachedTimestamp ); } } @@ -972,7 +972,7 @@ class Article implements Page { $cachedId = $pOutput->getCacheRevisionId(); if ( $cachedId !== null ) { $outputPage->setRevisionId( $cachedId ); - $outputPage->setRevisionTimestamp( $pOutput->getTimestamp() ); + $outputPage->getMetadata()->setRevisionTimestamp( $pOutput->getTimestamp() ); } } |