Skip to content

Commit

Permalink
Merge "Add protection indicators to mediawiki/core"
Browse files Browse the repository at this point in the history
  • Loading branch information
jenkins-bot authored and Gerrit Code Review committed Jun 19, 2024
2 parents 61d41a0 + bfb2d1d commit 3078fe7
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 0 deletions.
2 changes: 2 additions & 0 deletions RELEASE-NOTES-1.43
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ For notes on 1.42.x and older releases, see HISTORY.
More information: https://www.mediawiki.org/wiki/Heading_HTML_changes
In a future release the new markup will become the default,
and later this option will be removed.
* (T12347) $wgEnableProtectionIndicators - Defaults to false, setting it to true
shows a lock icon indicator on protected pages.
* …

==== Changed configuration ====
Expand Down
6 changes: 6 additions & 0 deletions docs/config-schema.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8066,3 +8066,9 @@ config-schema:
Whether to display a confirmation screen during user log out.
@unstable Temporary feature flag, T357484
@since 1.42
EnableProtectionIndicators:
default: false
type: boolean
description: |-
Whether to show indicators on a page when it is protected.
@since 1.43
6 changes: 6 additions & 0 deletions docs/config-vars.php
Original file line number Diff line number Diff line change
Expand Up @@ -4452,3 +4452,9 @@
* @see MediaWiki\MainConfigSchema::ShowLogoutConfirmation
*/
$wgShowLogoutConfirmation = null;

/**
* Config variable stub for the EnableProtectionIndicators setting, for use by phpdoc and IDEs.
* @see MediaWiki\MainConfigSchema::EnableProtectionIndicators
*/
$wgEnableProtectionIndicators = null;
6 changes: 6 additions & 0 deletions includes/MainConfigNames.php
Original file line number Diff line number Diff line change
Expand Up @@ -4468,4 +4468,10 @@ class MainConfigNames {
*/
public const ShowLogoutConfirmation = 'ShowLogoutConfirmation';

/**
* Name constant for the EnableProtectionIndicators setting, for use with Config::get()
* @see MainConfigSchema::EnableProtectionIndicators
*/
public const EnableProtectionIndicators = 'EnableProtectionIndicators';

}
10 changes: 10 additions & 0 deletions includes/MainConfigSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -12899,6 +12899,16 @@ public static function getDefaultReadOnlyFile( $uploadDirectory ): string {
'default' => false,
'type' => 'boolean',
];

/**
* Whether to show indicators on a page when it is protected.
*
* @since 1.43
*/
public const EnableProtectionIndicators = [
'default' => false,
'type' => 'boolean',
];
// endregion -- End Miscellaneous

}
2 changes: 2 additions & 0 deletions includes/config-schema.php
Original file line number Diff line number Diff line change
Expand Up @@ -2535,6 +2535,7 @@
'EditRecoveryExpiry' => 2592000,
'UseCodexSpecialBlock' => false,
'ShowLogoutConfirmation' => false,
'EnableProtectionIndicators' => false,
],
'type' => [
'ConfigRegistry' => 'object',
Expand Down Expand Up @@ -3013,6 +3014,7 @@
'EditRecoveryExpiry' => 'integer',
'UseCodexSpecialBlock' => 'boolean',
'ShowLogoutConfirmation' => 'boolean',
'EnableProtectionIndicators' => 'boolean',
],
'mergeStrategy' => [
'TiffThumbnailType' => 'replace',
Expand Down
89 changes: 89 additions & 0 deletions includes/page/Article.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
use MediaWiki\Parser\ParserOutput;
use MediaWiki\Permissions\Authority;
use MediaWiki\Permissions\PermissionStatus;
use MediaWiki\Permissions\RestrictionStore;
use MediaWiki\Revision\ArchivedRevisionLookup;
use MediaWiki\Revision\BadRevisionException;
use MediaWiki\Revision\RevisionRecord;
Expand Down Expand Up @@ -123,6 +124,8 @@ class Article implements Page {
protected IConnectionProvider $dbProvider;
protected DatabaseBlockStore $blockStore;

protected RestrictionStore $restrictionStore;

/**
* @var RevisionRecord|null Revision to be shown
*
Expand Down Expand Up @@ -150,6 +153,7 @@ public function __construct( Title $title, $oldId = null ) {
$this->archivedRevisionLookup = $services->getArchivedRevisionLookup();
$this->dbProvider = $services->getConnectionProvider();
$this->blockStore = $services->getDatabaseBlockStore();
$this->restrictionStore = $services->getRestrictionStore();
}

/**
Expand Down Expand Up @@ -481,6 +485,8 @@ public function view() {
return;
}

$this->showProtectionIndicator();

# Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
$outputPage->setPageTitle( Parser::formatPageTitle(
str_replace( '_', ' ', $this->getTitle()->getNsText() ),
Expand Down Expand Up @@ -582,6 +588,89 @@ public function view() {
}
}

/**
* Show a lock icon above the article body if the page is protected.
*/
public function showProtectionIndicator(): void {
$title = $this->getTitle();
$context = $this->getContext();
$outputPage = $context->getOutput();

$protectionIndicatorsAreEnabled = $context->getConfig()
->get( MainConfigNames::EnableProtectionIndicators );

if ( !$protectionIndicatorsAreEnabled || $title->isMainPage() ) {
return;
}

$protection = $this->restrictionStore->getRestrictions( $title, 'edit' );

$cascadeProtection = $this->restrictionStore->getCascadeProtectionSources( $title )[1];

$isCascadeProtected = array_key_exists( 'edit', $cascadeProtection );

if ( !$protection && !$isCascadeProtected ) {
return;
}

if ( $isCascadeProtected ) {
// Cascade-protected pages are protected at the sysop level. So it
// should not matter if we take the protection level of the first
// or last page that is being cascaded to the current page.
$protectionLevel = $cascadeProtection['edit'][0];
} else {
$protectionLevel = $protection[0];
}

// Protection levels are stored in the database as plain text, but
// they are expected to be valid protection levels. So we should be able to
// safely use them. However phan thinks this could be a XSS problem so we
// are being paranoid and escaping them once more.
$protectionLevel = htmlspecialchars( $protectionLevel );

$protectionExpiry = $this->restrictionStore->getRestrictionExpiry( $title, 'edit' );
$formattedProtectionExpiry = $context->getLanguage()
->formatExpiry( $protectionExpiry ?? '' );

$protectionMsg = 'protection-indicator-title';
if ( $protectionExpiry === 'infinity' || !$protectionExpiry ) {
$protectionMsg .= '-infinity';
}

// Potential values: 'protection-sysop', 'protection-autoconfirmed',
// 'protection-sysop-cascade' etc.
// If the wiki has more protection levels, the additional ids that get
// added take the form 'protection-<protectionLevel>' and
// 'protection-<protectionLevel>-cascade'.
$protectionIndicatorId = 'protection-' . $protectionLevel;
$protectionIndicatorId .= ( $isCascadeProtected ? '-cascade' : '' );

// Messages 'protection-indicator-title', 'protection-indicator-title-infinity'
$protectionMsg = $outputPage->msg( $protectionMsg, $protectionLevel, $formattedProtectionExpiry )->text();

// Use a trick similar to the one used in Action::addHelpLink() to allow wikis
// to customize where the help link points to.
$protectionHelpLink = $outputPage->msg( $protectionIndicatorId . '-helppage' );
if ( $protectionHelpLink->isDisabled() ) {
$protectionHelpLink = 'https://mediawiki.org/wiki/Special:MyLanguage/Help:Protection';
} else {
$protectionHelpLink = $protectionHelpLink->text();
}

$outputPage->setIndicators( [
$protectionIndicatorId => Html::rawElement( 'a', [
'class' => 'mw-protection-indicator-icon--lock',
'title' => $protectionMsg,
'href' => $protectionHelpLink
],
// Screen reader-only text describing the same thing as
// was mentioned in the title attribute.
Html::element( 'span', [], $protectionMsg ) )
] );

$outputPage->addModuleStyles( 'mediawiki.protectionIndicators.styles' );
}

/**
* Determines the desired ParserOutput and passes it to $outputPage.
*
Expand Down
2 changes: 2 additions & 0 deletions languages/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@
"querypage-no-updates": "Updates for this page are currently disabled.\nData here will not presently be refreshed.",
"querypage-updates-periodical": "Updates for this page are run periodically.",
"viewsource": "View source",
"protection-indicator-title-infinity": "This page is protected so that only users with the \"$1\" permission can edit it.",
"protection-indicator-title": "This page is protected so that only users with the \"$1\" permission can edit it until $2.",
"skin-action-viewsource": "View source",
"viewsource-title": "View source for $1",
"actionthrottled": "Action throttled",
Expand Down
2 changes: 2 additions & 0 deletions languages/i18n/qqq.json
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,8 @@
"querypage-no-updates": "Text on some special pages, e.g. [[Special:FewestRevisions]].",
"querypage-updates-periodical": "Text on some special pages which are configurated with a periodical run of a maintenance script.\n\nSee also {{msg-mw|querypage-no-updates}}.",
"viewsource": "The text displayed in place of the {{msg-mw|Edit}} tab when the user has no permission to edit the page.\n\nSee also:\n* {{msg-mw|Viewsource}}\n* {{msg-mw|Accesskey-ca-viewsource}}\n* {{msg-mw|Tooltip-ca-viewsource}}\n{{Identical|View source}}",
"protection-indicator-title": "Title text displayed when hovering over a lock icon that indicates a page is protected. Parameters:\n* $1 - protection level (e.g. \"autoconfirmed\")\n* $2 - expiry date (e.g. \"19 May 2024\")",
"protection-indicator-title-infinity": "Title text displayed when hovering over a lock icon that indicates a page is protected indefinitely. Parameters:\n* $1 - protection level (e.g. \"autoconfirmed\")",
"skin-action-viewsource": "{{Identical|viewsource}}",
"viewsource-title": "Page title shown when trying to edit a protected page. Parameters:\n* $1 - the name of the page",
"actionthrottled": "This is the title of an error page. Read it in combination with {{msg-mw|actionthrottledtext}}.",
Expand Down
3 changes: 3 additions & 0 deletions resources/Resources.php
Original file line number Diff line number Diff line change
Expand Up @@ -2310,6 +2310,9 @@
'ipb-hardblock'
],
],
'mediawiki.protectionIndicators.styles' => [
'styles' => 'resources/src/mediawiki.protectionIndicators/styles.less',
],
'mediawiki.special.changeslist' => [
'styles' => [
'resources/src/mediawiki.special.changeslist/changeslist.less'
Expand Down
12 changes: 12 additions & 0 deletions resources/src/mediawiki.protectionIndicators/styles.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import 'mediawiki.skin.variables.less';
@import 'mediawiki.mixins.less';

.mw-protection-indicator-icon {
&--lock {
.cdx-mixin-css-icon( @cdx-icon-lock );

> span {
.mixin-screen-reader-text();
}
}
}
52 changes: 52 additions & 0 deletions tests/phpunit/includes/page/ArticleTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -226,4 +226,56 @@ public function testOutputIsCached() {
$this->assertStringContainsString( 'Old Kittens', $article->getContext()->getOutput()->getHTML() );
}

/**
* Ensure that protection indicators are shown when the page is protected.
* @covers \Article::showProtectionIndicator
*/
public function testShowProtectionIndicator() {
$this->overrideConfigValue(
MainConfigNames::EnableProtectionIndicators,
true
);
$title = $this->getExistingTestPage()->getTitle();
$article = $this->newArticle( $title );

$wikiPage = new WikiPage( $title );
$cascade = false;
$wikiPage->doUpdateRestrictions( [
'edit' => 'autoconfirmed',
],
[ 'edit' => 'infinity' ],
$cascade,
'Test reason',
$this->getTestSysop()->getUser()
);

$article->showProtectionIndicator();
$output = $article->getContext()->getOutput();
$this->assertArrayHasKey( 'protection-autoconfirmed', $output->getIndicators(), 'Protection indicators are shown when a page is protected' );

$templateTitle = Title::newFromText( 'CascadeProtectionTest', NS_TEMPLATE );
$this->editPage( $templateTitle, 'Some text here', 'Test', NS_TEMPLATE, $this->getTestSysop()->getUser() );
$articleTitle = $this->getExistingTestPage()->getTitle();
$this->editPage( $articleTitle, '{{CascadeProtectionTest}}', 'Test', NS_MAIN, $this->getTestSysop()->getUser() );
$wikiPage = new WikiPage( $articleTitle );
$cascade = true;
$wikiPage->doUpdateRestrictions( [
'edit' => 'sysop',
],
[ 'edit' => 'infinity' ],
$cascade,
'Test reason',
$this->getTestSysop()->getUser()
);

$template = $this->newArticle( $templateTitle );

$template->showProtectionIndicator();
$output = $template->getContext()->getOutput();
$this->assertArrayHasKey(
'protection-sysop-cascade',
$output->getIndicators(),
'Protection indicators are shown when a page protected using cascade protection'
);
}
}

0 comments on commit 3078fe7

Please sign in to comment.